diff options
Diffstat (limited to 'modules/gdscript')
339 files changed, 4237 insertions, 628 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 51b3452a3a..631ee4d895 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -176,7 +176,8 @@ <method name="range" qualifiers="vararg"> <return type="Array" /> <description> - Returns an array with the given range. Range can be 1 argument N (0 to N-1), two arguments (initial, final-1) or three arguments (initial, final-1, increment). + Returns an array with the given range. Range can be 1 argument [code]N[/code] (0 to [code]N[/code] - 1), two arguments ([code]initial[/code], [code]final - 1[/code]) or three arguments ([code]initial[/code], [code]final - 1[/code], [code]increment[/code]). Returns an empty array if the range isn't valid (e.g. [code]range(2, 5, -1)[/code] or [code]range(5, 5, 1)[/code]). + Returns an array with the given range. [code]range()[/code] can have 1 argument N ([code]0[/code] to [code]N - 1[/code]), two arguments ([code]initial[/code], [code]final - 1[/code]) or three arguments ([code]initial[/code], [code]final - 1[/code], [code]increment[/code]). [code]increment[/code] can be negative. If [code]increment[/code] is negative, [code]final - 1[/code] will become [code]final + 1[/code]. Also, the initial value must be greater than the final value for the loop to run. [codeblock] print(range(4)) print(range(2, 5)) @@ -188,6 +189,20 @@ [2, 3, 4] [0, 2, 4] [/codeblock] + To iterate over an [Array] backwards, use: + [codeblock] + var array = [3, 6, 9] + var i := array.size() - 1 + while i >= 0: + print(array[i]) + i -= 1 + [/codeblock] + Output: + [codeblock] + 9 + 6 + 3 + [/codeblock] </description> </method> <method name="str" qualifiers="vararg"> diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml index 72738f027a..d45202bd40 100644 --- a/modules/gdscript/doc_classes/GDScript.xml +++ b/modules/gdscript/doc_classes/GDScript.xml @@ -30,6 +30,4 @@ </description> </method> </methods> - <constants> - </constants> </class> diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index ed8b0a4690..6529154e5c 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -45,7 +45,7 @@ static bool _is_bin_symbol(char32_t c) { return (c == '0' || c == '1'); } -Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) { +Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) { Dictionary color_map; Type next_type = NONE; @@ -449,35 +449,31 @@ void GDScriptSyntaxHighlighter::_update_cache() { color_region_cache.clear(); font_color = text_edit->get_theme_color(SNAME("font_color")); - symbol_color = EDITOR_GET("text_editor/highlighting/symbol_color"); - function_color = EDITOR_GET("text_editor/highlighting/function_color"); - number_color = EDITOR_GET("text_editor/highlighting/number_color"); - member_color = EDITOR_GET("text_editor/highlighting/member_variable_color"); + symbol_color = EDITOR_GET("text_editor/theme/highlighting/symbol_color"); + function_color = EDITOR_GET("text_editor/theme/highlighting/function_color"); + number_color = EDITOR_GET("text_editor/theme/highlighting/number_color"); + member_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color"); /* Engine types. */ - const Color types_color = EDITOR_GET("text_editor/highlighting/engine_type_color"); + const Color types_color = EDITOR_GET("text_editor/theme/highlighting/engine_type_color"); List<StringName> types; ClassDB::get_class_list(&types); for (const StringName &E : types) { - String n = E; - if (n.begins_with("_")) { - n = n.substr(1, n.length()); - } - keywords[n] = types_color; + keywords[E] = types_color; } /* User types. */ - const Color usertype_color = EDITOR_GET("text_editor/highlighting/user_type_color"); + const Color usertype_color = EDITOR_GET("text_editor/theme/highlighting/user_type_color"); List<StringName> global_classes; ScriptServer::get_global_class_list(&global_classes); for (const StringName &E : global_classes) { - keywords[String(E)] = usertype_color; + keywords[E] = usertype_color; } /* Autoloads. */ - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->value(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.value(); if (info.is_singleton) { keywords[info.name] = usertype_color; } @@ -486,28 +482,28 @@ void GDScriptSyntaxHighlighter::_update_cache() { const GDScriptLanguage *gdscript = GDScriptLanguage::get_singleton(); /* Core types. */ - const Color basetype_color = EDITOR_GET("text_editor/highlighting/base_type_color"); + const Color basetype_color = EDITOR_GET("text_editor/theme/highlighting/base_type_color"); List<String> core_types; gdscript->get_core_type_words(&core_types); for (const String &E : core_types) { - keywords[E] = basetype_color; + keywords[StringName(E)] = basetype_color; } /* Reserved words. */ - const Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color"); - const Color control_flow_keyword_color = EDITOR_GET("text_editor/highlighting/control_flow_keyword_color"); + const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color"); + const Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color"); List<String> keyword_list; gdscript->get_reserved_words(&keyword_list); for (const String &E : keyword_list) { if (gdscript->is_control_flow_keyword(E)) { - keywords[E] = control_flow_keyword_color; + keywords[StringName(E)] = control_flow_keyword_color; } else { - keywords[E] = keyword_color; + keywords[StringName(E)] = keyword_color; } } /* Comments */ - const Color comment_color = EDITOR_GET("text_editor/highlighting/comment_color"); + const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color"); List<String> comments; gdscript->get_comment_delimiters(&comments); for (const String &comment : comments) { @@ -517,7 +513,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { } /* Strings */ - const Color string_color = EDITOR_GET("text_editor/highlighting/string_color"); + const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color"); List<String> strings; gdscript->get_string_delimiters(&strings); for (const String &string : strings) { @@ -529,7 +525,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { const Ref<Script> script = _get_edited_resource(); if (script.is_valid()) { /* Member types. */ - const Color member_variable_color = EDITOR_GET("text_editor/highlighting/member_variable_color"); + const Color member_variable_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color"); StringName instance_base = script->get_instance_base_type(); if (instance_base != StringName()) { List<PropertyInfo> plist; @@ -566,28 +562,28 @@ void GDScriptSyntaxHighlighter::_update_cache() { annotation_color = Color(0.8, 0.5, 0.25); } - EDITOR_DEF("text_editor/highlighting/gdscript/function_definition_color", function_definition_color); - EDITOR_DEF("text_editor/highlighting/gdscript/node_path_color", node_path_color); - EDITOR_DEF("text_editor/highlighting/gdscript/annotation_color", annotation_color); + EDITOR_DEF("text_editor/theme/highlighting/gdscript/function_definition_color", function_definition_color); + EDITOR_DEF("text_editor/theme/highlighting/gdscript/node_path_color", node_path_color); + EDITOR_DEF("text_editor/theme/highlighting/gdscript/annotation_color", annotation_color); if (text_edit_color_theme == "Default" || godot_2_theme) { EditorSettings::get_singleton()->set_initial_value( - "text_editor/highlighting/gdscript/function_definition_color", + "text_editor/theme/highlighting/gdscript/function_definition_color", function_definition_color, true); EditorSettings::get_singleton()->set_initial_value( - "text_editor/highlighting/gdscript/node_path_color", + "text_editor/theme/highlighting/gdscript/node_path_color", node_path_color, true); EditorSettings::get_singleton()->set_initial_value( - "text_editor/highlighting/gdscript/annotation_color", + "text_editor/theme/highlighting/gdscript/annotation_color", annotation_color, true); } - function_definition_color = EDITOR_GET("text_editor/highlighting/gdscript/function_definition_color"); - node_path_color = EDITOR_GET("text_editor/highlighting/gdscript/node_path_color"); - annotation_color = EDITOR_GET("text_editor/highlighting/gdscript/annotation_color"); - type_color = EDITOR_GET("text_editor/highlighting/base_type_color"); + function_definition_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/function_definition_color"); + node_path_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/node_path_color"); + annotation_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/annotation_color"); + type_color = EDITOR_GET("text_editor/theme/highlighting/base_type_color"); } void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only) { diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h index d07c182aa6..07f21b34ae 100644 --- a/modules/gdscript/editor/gdscript_highlighter.h +++ b/modules/gdscript/editor/gdscript_highlighter.h @@ -47,8 +47,8 @@ private: Vector<ColorRegion> color_regions; Map<int, int> color_region_cache; - Dictionary keywords; - Dictionary member_keywords; + HashMap<StringName, Color> keywords; + HashMap<StringName, Color> member_keywords; enum Type { NONE, @@ -80,7 +80,7 @@ private: public: virtual void _update_cache() override; - virtual Dictionary _get_line_syntax_highlighting(int p_line) override; + virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) override; virtual String _get_name() const override; virtual Array _get_supported_languages() const override; diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 8957b00a1b..2bae838543 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -87,6 +87,19 @@ Object *GDScriptNativeClass::instantiate() { return ClassDB::instantiate(name); } +GDScriptFunction *GDScript::_super_constructor(GDScript *p_script) { + if (p_script->initializer) { + return p_script->initializer; + } else { + GDScript *base = p_script->_base; + if (base != nullptr) { + return _super_constructor(base); + } else { + return nullptr; + } + } +} + void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error) { GDScript *base = p_script->_base; if (base != nullptr) { @@ -109,8 +122,8 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco instance->owner_id = p_owner->get_instance_id(); #ifdef DEBUG_ENABLED //needed for hot reloading - for (Map<StringName, MemberInfo>::Element *E = member_indices.front(); E; E = E->next()) { - instance->member_indices_cache[E->key()] = E->get().index; + for (const KeyValue<StringName, MemberInfo> &E : member_indices) { + instance->member_indices_cache[E.key] = E.value.index; } #endif instance->owner->set_script_instance(instance); @@ -135,6 +148,8 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco if (p_argcount < 0) { return instance; } + + initializer = _super_constructor(this); if (initializer != nullptr) { initializer->call(instance, p_args, p_argcount, r_error); if (r_error.error != Callable::CallError::CALL_OK) { @@ -238,10 +253,10 @@ void GDScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) { void GDScript::_get_script_method_list(List<MethodInfo> *r_list, bool p_include_base) const { const GDScript *current = this; while (current) { - for (const Map<StringName, GDScriptFunction *>::Element *E = current->member_functions.front(); E; E = E->next()) { - GDScriptFunction *func = E->get(); + for (const KeyValue<StringName, GDScriptFunction *> &E : current->member_functions) { + GDScriptFunction *func = E.value; MethodInfo mi; - mi.name = E->key(); + mi.name = E.key; for (int i = 0; i < func->get_argument_count(); i++) { PropertyInfo arginfo = func->get_argument_type(i); #ifdef TOOLS_ENABLED @@ -271,11 +286,11 @@ void GDScript::_get_script_property_list(List<PropertyInfo> *r_list, bool p_incl while (sptr) { Vector<_GDScriptMemberSort> msort; - for (Map<StringName, PropertyInfo>::Element *E = sptr->member_info.front(); E; E = E->next()) { + for (const KeyValue<StringName, PropertyInfo> &E : sptr->member_info) { _GDScriptMemberSort ms; - ERR_CONTINUE(!sptr->member_indices.has(E->key())); - ms.index = sptr->member_indices[E->key()].index; - ms.name = E->key(); + ERR_CONTINUE(!sptr->member_indices.has(E.key)); + ms.index = sptr->member_indices[E.key].index; + ms.name = E.key; msort.push_back(ms); } @@ -397,8 +412,8 @@ void GDScript::_update_exports_values(Map<StringName, Variant> &values, List<Pro base_cache->_update_exports_values(values, propnames); } - for (Map<StringName, Variant>::Element *E = member_default_values_cache.front(); E; E = E->next()) { - values[E->key()] = E->get(); + for (const KeyValue<StringName, Variant> &E : member_default_values_cache) { + values[E.key] = E.value; } for (const PropertyInfo &E : members_cache) { @@ -456,9 +471,9 @@ void GDScript::_update_doc() { doc.description = doc_description; doc.tutorials = doc_tutorials; - for (Map<String, DocData::EnumDoc>::Element *E = doc_enums.front(); E; E = E->next()) { - if (E->value().description != "") { - doc.enums[E->key()] = E->value().description; + for (const KeyValue<String, DocData::EnumDoc> &E : doc_enums) { + if (E.value.description != "") { + doc.enums[E.key] = E.value.description; } } @@ -537,29 +552,29 @@ void GDScript::_update_doc() { doc.signals.push_back(signal_doc); } - for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { - if (subclasses.has(E->key())) { + for (const KeyValue<StringName, Variant> &E : constants) { + if (subclasses.has(E.key)) { continue; } // Enums. bool is_enum = false; - if (E->value().get_type() == Variant::DICTIONARY) { - if (doc_enums.has(E->key())) { + if (E.value.get_type() == Variant::DICTIONARY) { + if (doc_enums.has(E.key)) { is_enum = true; - for (int i = 0; i < doc_enums[E->key()].values.size(); i++) { - doc_enums[E->key()].values.write[i].enumeration = E->key(); - doc.constants.push_back(doc_enums[E->key()].values[i]); + for (int i = 0; i < doc_enums[E.key].values.size(); i++) { + doc_enums[E.key].values.write[i].enumeration = E.key; + doc.constants.push_back(doc_enums[E.key].values[i]); } } } if (!is_enum && doc_enums.has("@unnamed_enums")) { for (int i = 0; i < doc_enums["@unnamed_enums"].values.size(); i++) { - if (E->key() == doc_enums["@unnamed_enums"].values[i].name) { + if (E.key == doc_enums["@unnamed_enums"].values[i].name) { is_enum = true; DocData::ConstantDoc constant_doc; constant_doc.enumeration = "@unnamed_enums"; - DocData::constant_doc_from_variant(constant_doc, E->key(), E->value(), doc_enums["@unnamed_enums"].values[i].description); + DocData::constant_doc_from_variant(constant_doc, E.key, E.value, doc_enums["@unnamed_enums"].values[i].description); doc.constants.push_back(constant_doc); break; } @@ -568,16 +583,16 @@ void GDScript::_update_doc() { if (!is_enum) { DocData::ConstantDoc constant_doc; String doc_description; - if (doc_constants.has(E->key())) { - doc_description = doc_constants[E->key()]; + if (doc_constants.has(E.key)) { + doc_description = doc_constants[E.key]; } - DocData::constant_doc_from_variant(constant_doc, E->key(), E->value(), doc_description); + DocData::constant_doc_from_variant(constant_doc, E.key, E.value, doc_description); doc.constants.push_back(constant_doc); } } - for (Map<StringName, Ref<GDScript>>::Element *E = subclasses.front(); E; E = E->next()) { - E->get()->_update_doc(); + for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { + E.value->_update_doc(); } _add_doc(doc); @@ -625,9 +640,9 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc String path = ""; if (String(c->extends_path) != "" && String(c->extends_path) != get_path()) { path = c->extends_path; - if (path.is_rel_path()) { + if (path.is_relative_path()) { String base = get_path(); - if (base == "" || base.is_rel_path()) { + if (base == "" || base.is_relative_path()) { ERR_PRINT(("Could not resolve relative path for parent class: " + path).utf8().get_data()); } else { path = base.get_base_dir().plus_file(path); @@ -769,8 +784,8 @@ void GDScript::update_exports() { void GDScript::_set_subclass_path(Ref<GDScript> &p_sc, const String &p_path) { p_sc->path = p_path; - for (Map<StringName, Ref<GDScript>>::Element *E = p_sc->subclasses.front(); E; E = E->next()) { - _set_subclass_path(E->get(), p_path); + for (KeyValue<StringName, Ref<GDScript>> &E : p_sc->subclasses) { + _set_subclass_path(E.value, p_path); } } @@ -871,8 +886,8 @@ Error GDScript::reload(bool p_keep_state) { valid = true; - for (Map<StringName, Ref<GDScript>>::Element *E = subclasses.front(); E; E = E->next()) { - _set_subclass_path(E->get(), path); + for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { + _set_subclass_path(E.value, path); } _init_rpc_methods_properties(); @@ -886,8 +901,8 @@ ScriptLanguage *GDScript::get_language() const { void GDScript::get_constants(Map<StringName, Variant> *p_constants) { if (p_constants) { - for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { - (*p_constants)[E->key()] = E->value(); + for (const KeyValue<StringName, Variant> &E : constants) { + (*p_constants)[E.key] = E.value; } } } @@ -900,7 +915,7 @@ void GDScript::get_members(Set<StringName> *p_members) { } } -const Vector<MultiplayerAPI::RPCConfig> GDScript::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> GDScript::get_rpc_methods() const { return rpc_functions; } @@ -1017,9 +1032,9 @@ const Map<StringName, GDScriptFunction *> &GDScript::debug_get_member_functions( } StringName GDScript::debug_get_member_by_index(int p_idx) const { - for (const Map<StringName, MemberInfo>::Element *E = member_indices.front(); E; E = E->next()) { - if (E->get().index == p_idx) { - return E->key(); + for (const KeyValue<StringName, MemberInfo> &E : member_indices) { + if (E.value.index == p_idx) { + return E.key; } } @@ -1064,12 +1079,12 @@ bool GDScript::has_script_signal(const StringName &p_signal) const { } void GDScript::_get_script_signal_list(List<MethodInfo> *r_list, bool p_include_base) const { - for (const Map<StringName, Vector<StringName>>::Element *E = _signals.front(); E; E = E->next()) { + for (const KeyValue<StringName, Vector<StringName>> &E : _signals) { MethodInfo mi; - mi.name = E->key(); - for (int i = 0; i < E->get().size(); i++) { + mi.name = E.key; + for (int i = 0; i < E.value.size(); i++) { PropertyInfo arg; - arg.name = E->get()[i]; + arg.name = E.value[i]; mi.arguments.push_back(arg); } r_list->push_back(mi); @@ -1127,11 +1142,11 @@ void GDScript::_save_orphaned_subclasses() { }; Vector<ClassRefWithName> weak_subclasses; // collect subclasses ObjectID and name - for (Map<StringName, Ref<GDScript>>::Element *E = subclasses.front(); E; E = E->next()) { - E->get()->_owner = nullptr; //bye, you are no longer owned cause I died + for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { + E.value->_owner = nullptr; //bye, you are no longer owned cause I died ClassRefWithName subclass; - subclass.id = E->get()->get_instance_id(); - subclass.fully_qualified_name = E->get()->fully_qualified_name; + subclass.id = E.value->get_instance_id(); + subclass.fully_qualified_name = E.value->fully_qualified_name; weak_subclasses.push_back(subclass); } @@ -1163,10 +1178,10 @@ void GDScript::_init_rpc_methods_properties() { Map<StringName, Ref<GDScript>>::Element *sub_E = subclasses.front(); while (cscript) { // RPC Methods - for (Map<StringName, GDScriptFunction *>::Element *E = cscript->member_functions.front(); E; E = E->next()) { - MultiplayerAPI::RPCConfig config = E->get()->get_rpc_config(); - if (config.rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) { - config.name = E->get()->get_name(); + for (KeyValue<StringName, GDScriptFunction *> &E : cscript->member_functions) { + Multiplayer::RPCConfig config = E.value->get_rpc_config(); + if (config.rpc_mode != Multiplayer::RPC_MODE_DISABLED) { + config.name = E.value->get_name(); if (rpc_functions.find(config) == -1) { rpc_functions.push_back(config); } @@ -1185,7 +1200,7 @@ void GDScript::_init_rpc_methods_properties() { } // Sort so we are 100% that they are always the same. - rpc_functions.sort_custom<MultiplayerAPI::SortRPCConfig>(); + rpc_functions.sort_custom<Multiplayer::SortRPCConfig>(); } GDScript::~GDScript() { @@ -1200,8 +1215,8 @@ GDScript::~GDScript() { } } - for (Map<StringName, GDScriptFunction *>::Element *E = member_functions.front(); E; E = E->next()) { - memdelete(E->get()); + for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { + memdelete(E.value); } if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown. @@ -1427,11 +1442,11 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const //instance a fake script for editing the values Vector<_GDScriptMemberSort> msort; - for (Map<StringName, PropertyInfo>::Element *F = sptr->member_info.front(); F; F = F->next()) { + for (const KeyValue<StringName, PropertyInfo> &F : sptr->member_info) { _GDScriptMemberSort ms; - ERR_CONTINUE(!sptr->member_indices.has(F->key())); - ms.index = sptr->member_indices[F->key()].index; - ms.name = F->key(); + ERR_CONTINUE(!sptr->member_indices.has(F.key)); + ms.index = sptr->member_indices[F.key].index; + ms.name = F.key; msort.push_back(ms); } @@ -1452,11 +1467,11 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const void GDScriptInstance::get_method_list(List<MethodInfo> *p_list) const { const GDScript *sptr = script.ptr(); while (sptr) { - for (Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.front(); E; E = E->next()) { + for (const KeyValue<StringName, GDScriptFunction *> &E : sptr->member_functions) { MethodInfo mi; - mi.name = E->key(); + mi.name = E.key; mi.flags |= METHOD_FLAG_FROM_SCRIPT; - for (int i = 0; i < E->get()->get_argument_count(); i++) { + for (int i = 0; i < E.value->get_argument_count(); i++) { mi.arguments.push_back(PropertyInfo(Variant::NIL, "arg" + itos(i))); } p_list->push_back(mi); @@ -1541,7 +1556,7 @@ ScriptLanguage *GDScriptInstance::get_language() { return GDScriptLanguage::get_singleton(); } -const Vector<MultiplayerAPI::RPCConfig> GDScriptInstance::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> GDScriptInstance::get_rpc_methods() const { return script->get_rpc_methods(); } @@ -1554,10 +1569,10 @@ void GDScriptInstance::reload_members() { new_members.resize(script->member_indices.size()); //pass the values to the new indices - for (Map<StringName, GDScript::MemberInfo>::Element *E = script->member_indices.front(); E; E = E->next()) { - if (member_indices_cache.has(E->key())) { - Variant value = members[member_indices_cache[E->key()]]; - new_members.write[E->get().index] = value; + for (KeyValue<StringName, GDScript::MemberInfo> &E : script->member_indices) { + if (member_indices_cache.has(E.key)) { + Variant value = members[member_indices_cache[E.key]]; + new_members.write[E.value.index] = value; } } @@ -1566,8 +1581,8 @@ void GDScriptInstance::reload_members() { //pass the values to the new indices member_indices_cache.clear(); - for (Map<StringName, GDScript::MemberInfo>::Element *E = script->member_indices.front(); E; E = E->next()) { - member_indices_cache[E->key()] = E->get().index; + for (const KeyValue<StringName, GDScript::MemberInfo> &E : script->member_indices) { + member_indices_cache[E.key] = E.value.index; } #endif @@ -1644,16 +1659,11 @@ void GDScriptLanguage::init() { List<StringName> class_list; ClassDB::get_class_list(&class_list); for (const StringName &n : class_list) { - String s = String(n); - if (s.begins_with("_")) { - s = s.substr(1, s.length()); - } - - if (globals.has(s)) { + if (globals.has(n)) { continue; } Ref<GDScriptNativeClass> nc = memnew(GDScriptNativeClass(n)); - _add_global(s, nc); + _add_global(n, nc); } //populate singletons @@ -1880,21 +1890,21 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so #endif - for (Map<ObjectID, List<Pair<StringName, Variant>>>::Element *F = script->pending_reload_state.front(); F; F = F->next()) { - map[F->key()] = F->get(); //pending to reload, use this one instead + for (const KeyValue<ObjectID, List<Pair<StringName, Variant>>> &F : script->pending_reload_state) { + map[F.key] = F.value; //pending to reload, use this one instead } } } - for (Map<Ref<GDScript>, Map<ObjectID, List<Pair<StringName, Variant>>>>::Element *E = to_reload.front(); E; E = E->next()) { - Ref<GDScript> scr = E->key(); + for (KeyValue<Ref<GDScript>, Map<ObjectID, List<Pair<StringName, Variant>>>> &E : to_reload) { + Ref<GDScript> scr = E.key; scr->reload(p_soft_reload); //restore state if saved - for (Map<ObjectID, List<Pair<StringName, Variant>>>::Element *F = E->get().front(); F; F = F->next()) { - List<Pair<StringName, Variant>> &saved_state = F->get(); + for (KeyValue<ObjectID, List<Pair<StringName, Variant>>> &F : E.value) { + List<Pair<StringName, Variant>> &saved_state = F.value; - Object *obj = ObjectDB::get_instance(F->key()); + Object *obj = ObjectDB::get_instance(F.key); if (!obj) { continue; } @@ -2064,7 +2074,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b if (r_icon_path) { if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) { *r_icon_path = c->icon_path; - } else if (c->icon_path.is_rel_path()) { + } else if (c->icon_path.is_relative_path()) { *r_icon_path = p_path.get_base_dir().plus_file(c->icon_path).simplify_path(); } } @@ -2092,7 +2102,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b break; } String subpath = subclass->extends_path; - if (subpath.is_rel_path()) { + if (subpath.is_relative_path()) { subpath = path.get_base_dir().plus_file(subpath).simplify_path(); } @@ -2200,15 +2210,15 @@ GDScriptLanguage::~GDScriptLanguage() { // is not the same as before). script->reference(); - for (Map<StringName, GDScriptFunction *>::Element *E = script->member_functions.front(); E; E = E->next()) { - GDScriptFunction *func = E->get(); + for (KeyValue<StringName, GDScriptFunction *> &E : script->member_functions) { + GDScriptFunction *func = E.value; for (int i = 0; i < func->argument_types.size(); i++) { func->argument_types.write[i].script_type_ref = Ref<Script>(); } func->return_type.script_type_ref = Ref<Script>(); } - for (Map<StringName, GDScript::MemberInfo>::Element *E = script->member_indices.front(); E; E = E->next()) { - E->get().data_type.script_type_ref = Ref<Script>(); + for (KeyValue<StringName, GDScript::MemberInfo> &E : script->member_indices) { + E.value.data_type.script_type_ref = Ref<Script>(); } s = s->next(); diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 24809ad5fd..791f8a1431 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -85,7 +85,7 @@ class GDScript : public Script { Map<StringName, MemberInfo> member_indices; //members are just indices to the instantiated script. Map<StringName, Ref<GDScript>> subclasses; Map<StringName, Vector<StringName>> _signals; - Vector<MultiplayerAPI::RPCConfig> rpc_functions; + Vector<Multiplayer::RPCConfig> rpc_functions; #ifdef TOOLS_ENABLED @@ -130,6 +130,7 @@ class GDScript : public Script { SelfList<GDScriptFunctionState>::List pending_func_states; + GDScriptFunction *_super_constructor(GDScript *p_script); void _super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error); GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error); @@ -245,7 +246,7 @@ public: virtual void get_constants(Map<StringName, Variant> *p_constants) override; virtual void get_members(Set<StringName> *p_members) override; - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override; #ifdef TOOLS_ENABLED virtual bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; } @@ -298,7 +299,7 @@ public: void reload_members(); - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const; GDScriptInstance(); ~GDScriptInstance(); diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index ab37e54cf1..ac031baa8c 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -30,6 +30,7 @@ #include "gdscript_analyzer.h" +#include "core/config/engine.h" #include "core/config/project_settings.h" #include "core/io/file_access.h" #include "core/io/resource_loader.h" @@ -112,11 +113,10 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_native type.is_meta_type = true; List<StringName> enum_values; - StringName real_native_name = GDScriptParser::get_real_class_name(p_native_class); - ClassDB::get_enum_constants(real_native_name, p_enum_name, &enum_values); + ClassDB::get_enum_constants(p_native_class, p_enum_name, &enum_values); for (const StringName &E : enum_values) { - type.enum_values[E] = ClassDB::get_integer_constant(real_native_name, E); + type.enum_values[E] = ClassDB::get_integer_constant(p_native_class, E); } return type; @@ -132,6 +132,81 @@ static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) { return type; } +bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class) { + if (p_class->members_indices.has(p_member_name)) { + int index = p_class->members_indices[p_member_name]; + const GDScriptParser::ClassNode::Member *member = &p_class->members[index]; + + if (member->type == GDScriptParser::ClassNode::Member::VARIABLE || + member->type == GDScriptParser::ClassNode::Member::CONSTANT || + member->type == GDScriptParser::ClassNode::Member::ENUM || + member->type == GDScriptParser::ClassNode::Member::ENUM_VALUE || + member->type == GDScriptParser::ClassNode::Member::CLASS || + member->type == GDScriptParser::ClassNode::Member::SIGNAL) { + return true; + } + } + + return false; +} + +bool GDScriptAnalyzer::has_member_name_conflict_in_native_type(const StringName &p_member_name, const StringName &p_native_type_string) { + if (ClassDB::has_signal(p_native_type_string, p_member_name)) { + return true; + } + if (ClassDB::has_property(p_native_type_string, p_member_name)) { + return true; + } + if (ClassDB::has_integer_constant(p_native_type_string, p_member_name)) { + return true; + } + + return false; +} + +Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string) { + if (has_member_name_conflict_in_native_type(p_member_name, p_native_type_string)) { + push_error(vformat(R"(Member "%s" redefined (original in native class '%s'))", p_member_name, p_native_type_string), p_member_node); + return ERR_PARSE_ERROR; + } + + if (class_exists(p_member_name)) { + push_error(vformat(R"(The member "%s" shadows a native class.)", p_member_name), p_member_node); + return ERR_PARSE_ERROR; + } + + if (GDScriptParser::get_builtin_type(p_member_name) != Variant::VARIANT_MAX) { + push_error(vformat(R"(The member "%s" cannot have the same name as a builtin type.)", p_member_name), p_member_node); + return ERR_PARSE_ERROR; + } + + return OK; +} + +Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node) { + const GDScriptParser::DataType *current_data_type = &p_class_node->base_type; + while (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::CLASS) { + GDScriptParser::ClassNode *current_class_node = current_data_type->class_type; + if (has_member_name_conflict_in_script_class(p_member_name, current_class_node)) { + push_error(vformat(R"(The member "%s" already exists in a parent class.)", p_member_name), + p_member_node); + return ERR_PARSE_ERROR; + } + current_data_type = ¤t_class_node->base_type; + } + + if (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::NATIVE) { + if (current_data_type->native_type != StringName("")) { + return check_native_member_name_conflict( + p_member_name, + p_member_node, + current_data_type->native_type); + } + } + + return OK; +} + Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) { if (p_class->base_type.is_set()) { // Already resolved @@ -148,6 +223,17 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, p_class->fqcn = p_class->outer->fqcn + "::" + String(p_class->identifier->name); } + if (p_class->identifier) { + StringName class_name = p_class->identifier->name; + if (class_exists(class_name)) { + push_error(vformat(R"(Class "%s" hides a native class.)", class_name), p_class->identifier); + } else if (ScriptServer::is_global_class(class_name) && (ScriptServer::get_global_class_path(class_name) != parser->script_path || p_class != parser->head)) { + push_error(vformat(R"(Class "%s" hides a global script class.)", class_name), p_class->identifier); + } else if (ProjectSettings::get_singleton()->has_autoload(class_name) && ProjectSettings::get_singleton()->get_autoload(class_name).is_singleton) { + push_error(vformat(R"(Class "%s" hides an autoload singleton.)", class_name), p_class->identifier); + } + } + GDScriptParser::DataType result; // Set datatype for class. @@ -172,6 +258,9 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, int extends_index = 0; if (!p_class->extends_path.is_empty()) { + if (p_class->extends_path.is_relative_path()) { + p_class->extends_path = class_type.script_path.get_base_dir().plus_file(p_class->extends_path).simplify_path(); + } Ref<GDScriptParserRef> parser = get_parser_for(p_class->extends_path); if (parser.is_null()) { push_error(vformat(R"(Could not resolve super class path "%s".)", p_class->extends_path), p_class); @@ -229,7 +318,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); return err; } - } else if (class_exists(name) && ClassDB::can_instantiate(GDScriptParser::get_real_class_name(name))) { + } else if (class_exists(name) && ClassDB::can_instantiate(name)) { base.kind = GDScriptParser::DataType::NATIVE; base.native_type = name; } else { @@ -406,7 +495,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return GDScriptParser::DataType(); } result = ref->get_parser()->head->get_datatype(); - } else if (ClassDB::has_enum(GDScriptParser::get_real_class_name(parser->current_class->base_type.native_type), first)) { + } else if (ClassDB::has_enum(parser->current_class->base_type.native_type, first)) { // Native enum in current class. result = make_native_enum_type(parser->current_class->base_type.native_type, first); } else { @@ -433,8 +522,28 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type case GDScriptParser::ClassNode::Member::CONSTANT: if (member.constant->get_datatype().is_meta_type) { result = member.constant->get_datatype(); + result.is_meta_type = false; found = true; break; + } else if (Ref<Script>(member.constant->initializer->reduced_value).is_valid()) { + Ref<GDScript> gdscript = member.constant->initializer->reduced_value; + if (gdscript.is_valid()) { + Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_path()); + if (ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) { + push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_path()), p_type); + return GDScriptParser::DataType(); + } + result = ref->get_parser()->head->get_datatype(); + result.is_meta_type = false; + } else { + Ref<GDScript> script = member.constant->initializer->reduced_value; + result.kind = GDScriptParser::DataType::SCRIPT; + result.builtin_type = Variant::OBJECT; + result.script_type = script; + result.script_path = script->get_path(); + result.native_type = script->get_instance_base_type(); + } + break; } [[fallthrough]]; default: @@ -469,7 +578,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } } else if (result.kind == GDScriptParser::DataType::NATIVE) { // Only enums allowed for native. - if (ClassDB::has_enum(GDScriptParser::get_real_class_name(result.native_type), p_type->type_chain[1]->name)) { + if (ClassDB::has_enum(result.native_type, p_type->type_chain[1]->name)) { if (p_type->type_chain.size() > 2) { push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[2]); } else { @@ -505,6 +614,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas switch (member.type) { case GDScriptParser::ClassNode::Member::VARIABLE: { + check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable); + GDScriptParser::DataType datatype; datatype.kind = GDScriptParser::DataType::VARIANT; datatype.type_source = GDScriptParser::DataType::UNDETECTED; @@ -578,6 +689,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } } break; case GDScriptParser::ClassNode::Member::CONSTANT: { + check_class_member_name_conflict(p_class, member.constant->identifier->name, member.constant); + reduce_expression(member.constant->initializer); GDScriptParser::DataType specified_type; @@ -587,8 +700,9 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas specified_type.is_meta_type = false; } - GDScriptParser::DataType datatype = member.constant->get_datatype(); + GDScriptParser::DataType datatype; if (member.constant->initializer) { + datatype = member.constant->initializer->get_datatype(); if (member.constant->initializer->type == GDScriptParser::Node::ARRAY) { GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(member.constant->initializer); const_fold_array(array); @@ -627,6 +741,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } } break; case GDScriptParser::ClassNode::Member::SIGNAL: { + check_class_member_name_conflict(p_class, member.signal->identifier->name, member.signal); + for (int j = 0; j < member.signal->parameters.size(); j++) { GDScriptParser::DataType signal_type = resolve_datatype(member.signal->parameters[j]->datatype_specifier); signal_type.is_meta_type = false; @@ -646,6 +762,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } } break; case GDScriptParser::ClassNode::Member::ENUM: { + check_class_member_name_conflict(p_class, member.m_enum->identifier->name, member.m_enum); + GDScriptParser::DataType enum_type; enum_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; enum_type.kind = GDScriptParser::DataType::ENUM; @@ -697,6 +815,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas break; case GDScriptParser::ClassNode::Member::ENUM_VALUE: { if (member.enum_value.custom_value) { + check_class_member_name_conflict(p_class, member.enum_value.identifier->name, member.enum_value.custom_value); + current_enum = member.enum_value.parent_enum; reduce_expression(member.enum_value.custom_value); current_enum = nullptr; @@ -710,6 +830,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas member.enum_value.resolved = true; } } else { + check_class_member_name_conflict(p_class, member.enum_value.identifier->name, member.enum_value.parent_enum); + if (member.enum_value.index > 0) { member.enum_value.value = member.enum_value.parent_enum->values[member.enum_value.index - 1].value + 1; } else { @@ -722,7 +844,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas p_class->members.write[i].enum_value = member.enum_value; } break; case GDScriptParser::ClassNode::Member::CLASS: - break; // Done later. + check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class); + break; case GDScriptParser::ClassNode::Member::UNDEFINED: ERR_PRINT("Trying to resolve undefined member."); break; @@ -912,7 +1035,10 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * return_type.is_meta_type = false; p_function->set_datatype(return_type); if (p_function->return_type) { - push_error("Constructor cannot have an explicit return type.", p_function->return_type); + GDScriptParser::DataType declared_return = resolve_datatype(p_function->return_type); + if (declared_return.kind != GDScriptParser::DataType::BUILTIN || declared_return.builtin_type != Variant::NIL) { + push_error("Constructor cannot have an explicit return type.", p_function->return_type); + } } } else { GDScriptParser::DataType return_type = resolve_datatype(p_function->return_type); @@ -1069,12 +1195,30 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { } } - if (!list_resolved) { + GDScriptParser::DataType variable_type; + if (list_resolved) { + variable_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + variable_type.kind = GDScriptParser::DataType::BUILTIN; + variable_type.builtin_type = Variant::INT; // Can this ever be a float or something else? + p_for->variable->set_datatype(variable_type); + } else if (p_for->list) { resolve_node(p_for->list); + if (p_for->list->datatype.has_container_element_type()) { + variable_type = p_for->list->datatype.get_container_element_type(); + variable_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + } else if (p_for->list->datatype.is_typed_container_type()) { + variable_type = p_for->list->datatype.get_typed_container_type(); + variable_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + } else { + // Last resort + // TODO: Must other cases be handled? Must we mark as unsafe? + variable_type.type_source = GDScriptParser::DataType::UNDETECTED; + variable_type.kind = GDScriptParser::DataType::VARIANT; + } + } + if (p_for->variable) { + p_for->variable->set_datatype(variable_type); } - - // TODO: If list is a typed array, the variable should be an element. - // Also applicable for constant range() (so variable is int or float). resolve_suite(p_for->loop); p_for->set_datatype(p_for->loop->get_datatype()); @@ -1362,8 +1506,7 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame } if (p_parameter->datatype_specifier != nullptr) { - resolve_datatype(p_parameter->datatype_specifier); - result = p_parameter->datatype_specifier->get_datatype(); + result = resolve_datatype(p_parameter->datatype_specifier); result.is_meta_type = false; if (p_parameter->default_value != nullptr) { @@ -1375,6 +1518,10 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame } } + if (result.builtin_type == Variant::Type::NIL && result.type_source == GDScriptParser::DataType::ANNOTATED_INFERRED && p_parameter->datatype_specifier == nullptr) { + push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is "null".)", p_parameter->identifier->name), p_parameter->default_value); + } + p_parameter->set_datatype(result); } @@ -1617,7 +1764,6 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig } else { // TODO: Warning in this case. mark_node_unsafe(p_assignment); - p_assignment->use_conversion_assign = true; } } } else { @@ -1645,6 +1791,15 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig identifier->variable_source->set_datatype(id_type); } } break; + case GDScriptParser::IdentifierNode::FUNCTION_PARAMETER: { + GDScriptParser::DataType id_type = identifier->parameter_source->get_datatype(); + if (!id_type.is_hard_type()) { + id_type = assigned_value_type; + id_type.type_source = GDScriptParser::DataType::INFERRED; + id_type.is_constant = false; + identifier->parameter_source->set_datatype(id_type); + } + } break; case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: { GDScriptParser::DataType id_type = identifier->variable_source->get_datatype(); if (!id_type.is_hard_type()) { @@ -2120,16 +2275,19 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa if (get_function_signature(p_call, base_type, p_call->function_name, return_type, par_types, default_arg_count, is_static, is_vararg)) { // If the function require typed arrays we must make literals be typed. - for (Map<int, GDScriptParser::ArrayNode *>::Element *E = arrays.front(); E; E = E->next()) { - int index = E->key(); + for (const KeyValue<int, GDScriptParser::ArrayNode *> &E : arrays) { + int index = E.key; if (index < par_types.size() && par_types[index].has_container_element_type()) { - update_array_literal_element_type(par_types[index], E->get()); + update_array_literal_element_type(par_types[index], E.value); } } validate_call_arg(par_types, default_arg_count, is_vararg, p_call); if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) { push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parser->current_function->identifier->name), p_call->callee); + } else if (!is_self && base_type.is_meta_type && !is_static) { + base_type.is_meta_type = false; // For `to_string()`. + push_error(vformat(R"*(Cannot call non-static function "%s()" on the class "%s" directly. Make an instance instead.)*", p_call->function_name, base_type.to_string()), p_call->callee); } else if (is_self && !is_static && !lambda_stack.is_empty()) { push_error(vformat(R"*(Cannot call non-static function "%s()" from a lambda function.)*", p_call->function_name), p_call->callee); } @@ -2252,7 +2410,7 @@ void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) result.native_type = "Node"; result.builtin_type = Variant::OBJECT; - if (!ClassDB::is_parent_class(GDScriptParser::get_real_class_name(parser->current_class->base_type.native_type), result.native_type)) { + if (!ClassDB::is_parent_class(parser->current_class->base_type.native_type, result.native_type)) { push_error(R"*(Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node.)*", p_get_node); } else if (!lambda_stack.is_empty()) { push_error(R"*(Cannot use shorthand "get_node()" notation ("$") inside a lambda. Use a captured variable instead.)*", p_get_node); @@ -2303,13 +2461,15 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod p_identifier->is_constant = true; p_identifier->reduced_value = result; p_identifier->set_datatype(type_from_variant(result, p_identifier)); - } else { + } else if (base.is_hard_type()) { push_error(vformat(R"(Cannot find constant "%s" on type "%s".)", name, base.to_string()), p_identifier); } } else { switch (base.builtin_type) { case Variant::NIL: { - push_error(vformat(R"(Invalid get index "%s" on base Nil)", name), p_identifier); + if (base.is_hard_type()) { + push_error(vformat(R"(Invalid get index "%s" on base Nil)", name), p_identifier); + } return; } case Variant::DICTIONARY: { @@ -2330,7 +2490,9 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod return; } } - push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier); + if (base.is_hard_type()) { + push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier); + } } } } @@ -2351,7 +2513,10 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod result.enum_type = name; p_identifier->set_datatype(result); } else { - push_error(vformat(R"(Cannot find value "%s" in "%s".)", name, base.to_string()), p_identifier); + // Consider as a Dictionary + GDScriptParser::DataType dummy; + dummy.kind = GDScriptParser::DataType::VARIANT; + p_identifier->set_datatype(dummy); } } else { push_error(R"(Cannot get property from enum value.)", p_identifier); @@ -2388,11 +2553,17 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod case GDScriptParser::ClassNode::Member::VARIABLE: p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE; p_identifier->variable_source = member.variable; + member.variable->usages += 1; break; case GDScriptParser::ClassNode::Member::FUNCTION: resolve_function_signature(member.function); p_identifier->set_datatype(make_callable_type(member.function->info)); break; + case GDScriptParser::ClassNode::Member::CLASS: + // For out-of-order resolution: + resolve_class_interface(member.m_class); + p_identifier->set_datatype(member.m_class->get_datatype()); + break; default: break; // Type already set. } @@ -2404,14 +2575,29 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod while (outer != nullptr) { if (outer->has_member(name)) { const GDScriptParser::ClassNode::Member &member = outer->get_member(name); - if (member.type == GDScriptParser::ClassNode::Member::CONSTANT) { - // TODO: Make sure loops won't cause problem. And make special error message for those. - // For out-of-order resolution: - reduce_expression(member.constant->initializer); - p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = true; - p_identifier->reduced_value = member.constant->initializer->reduced_value; - return; + switch (member.type) { + case GDScriptParser::ClassNode::Member::CONSTANT: { + // TODO: Make sure loops won't cause problem. And make special error message for those. + // For out-of-order resolution: + reduce_expression(member.constant->initializer); + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.constant->initializer->reduced_value; + return; + } break; + case GDScriptParser::ClassNode::Member::ENUM_VALUE: { + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.enum_value.value; + return; + } break; + case GDScriptParser::ClassNode::Member::ENUM: { + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = false; + return; + } break; + default: + break; } } outer = outer->outer; @@ -2421,7 +2607,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } // Check native members. - const StringName &native = GDScriptParser::get_real_class_name(base.native_type); + const StringName &native = base.native_type; if (class_exists(native)) { PropertyInfo prop_info; @@ -2699,7 +2885,7 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { } else { p_preload->resolved_path = p_preload->path->reduced_value; // TODO: Save this as script dependency. - if (p_preload->resolved_path.is_rel_path()) { + if (p_preload->resolved_path.is_relative_path()) { p_preload->resolved_path = parser->script_path.get_base_dir().plus_file(p_preload->resolved_path); } p_preload->resolved_path = p_preload->resolved_path.simplify_path(); @@ -2725,6 +2911,9 @@ void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) { } void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscript) { + if (p_subscript->base == nullptr) { + return; + } if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER) { reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base), true); } else { @@ -3202,7 +3391,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo return result; } -bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) { +bool GDScriptAnalyzer::get_function_signature(GDScriptParser::CallNode *p_source, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) { r_static = false; r_vararg = false; r_default_arg_count = 0; @@ -3221,14 +3410,16 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD for (const MethodInfo &E : methods) { if (E.name == p_function) { - return function_signature_from_info(E, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + function_signature_from_info(E, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + r_static = Variant::is_builtin_method_static(p_base_type.builtin_type, function_name); + return true; } } return false; } - bool is_constructor = p_base_type.is_meta_type && p_function == "new"; + bool is_constructor = (p_base_type.is_meta_type || (p_source->callee && p_source->callee->type == GDScriptParser::Node::IDENTIFIER)) && p_function == StaticCString::create("new"); if (is_constructor) { function_name = "_init"; r_static = true; @@ -3258,6 +3449,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD } } r_return_type = found_function->get_datatype(); + r_return_type.is_meta_type = false; r_return_type.is_coroutine = found_function->is_coroutine; return true; @@ -3303,11 +3495,13 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD return true; } - StringName real_native = GDScriptParser::get_real_class_name(base_native); - MethodInfo info; - if (ClassDB::get_method_info(real_native, function_name, &info)) { - return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + if (ClassDB::get_method_info(base_native, function_name, &info)) { + bool valid = function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + if (valid && Engine::get_singleton()->has_singleton(base_native)) { + r_static = true; + } + return valid; } return false; @@ -3398,24 +3592,23 @@ bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con StringName parent = base_native; while (parent != StringName()) { - StringName real_class_name = GDScriptParser::get_real_class_name(parent); - if (ClassDB::has_method(real_class_name, name, true)) { + if (ClassDB::has_method(parent, name, true)) { parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "method", parent); return true; - } else if (ClassDB::has_signal(real_class_name, name, true)) { + } else if (ClassDB::has_signal(parent, name, true)) { parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "signal", parent); return true; - } else if (ClassDB::has_property(real_class_name, name, true)) { + } else if (ClassDB::has_property(parent, name, true)) { parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "property", parent); return true; - } else if (ClassDB::has_integer_constant(real_class_name, name, true)) { + } else if (ClassDB::has_integer_constant(parent, name, true)) { parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "constant", parent); return true; - } else if (ClassDB::has_enum(real_class_name, name, true)) { + } else if (ClassDB::has_enum(parent, name, true)) { parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "enum", parent); return true; } - parent = ClassDB::get_parent_class(real_class_name); + parent = ClassDB::get_parent_class(parent); } return false; @@ -3496,6 +3689,11 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) { return true; } + if (p_source.kind == GDScriptParser::DataType::ENUM) { + if (p_source.native_type == p_target.native_type) { + return true; + } + } if (p_source.kind == GDScriptParser::DataType::ENUM_VALUE) { if (p_source.native_type == p_target.native_type && p_target.enum_values.has(p_source.enum_type)) { return true; @@ -3560,16 +3758,12 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ break; // Already solved before. } - // Get underscore-prefixed version for some classes. - src_native = GDScriptParser::get_real_class_name(src_native); - switch (p_target.kind) { case GDScriptParser::DataType::NATIVE: { if (p_target.is_meta_type) { return ClassDB::is_parent_class(src_native, GDScriptNativeClass::get_class_static()); } - StringName tgt_native = GDScriptParser::get_real_class_name(p_target.native_type); - return ClassDB::is_parent_class(src_native, tgt_native); + return ClassDB::is_parent_class(src_native, p_target.native_type); } case GDScriptParser::DataType::SCRIPT: if (p_target.is_meta_type) { @@ -3618,8 +3812,7 @@ void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) { } bool GDScriptAnalyzer::class_exists(const StringName &p_class) const { - StringName real_name = GDScriptParser::get_real_class_name(p_class); - return ClassDB::class_exists(real_name) && ClassDB::is_class_exposed(real_name); + return ClassDB::class_exists(p_class) && ClassDB::is_class_exposed(p_class); } Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) { diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 8cd3fcf837..2e17e15452 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -44,6 +44,12 @@ class GDScriptAnalyzer { const GDScriptParser::EnumNode *current_enum = nullptr; List<const GDScriptParser::LambdaNode *> lambda_stack; + // Tests for detecting invalid overloading of script members + static _FORCE_INLINE_ bool has_member_name_conflict_in_script_class(const StringName &p_name, const GDScriptParser::ClassNode *p_current_class_node); + static _FORCE_INLINE_ bool has_member_name_conflict_in_native_type(const StringName &p_name, const StringName &p_native_type_string); + Error check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string); + Error check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node); + Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true); GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type); @@ -99,7 +105,7 @@ class GDScriptAnalyzer { GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type) const; GDScriptParser::DataType type_from_property(const PropertyInfo &p_property) const; GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source); - bool get_function_signature(GDScriptParser::Node *p_source, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); + bool get_function_signature(GDScriptParser::CallNode *p_source, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); bool validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call); bool validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 5958326315..b8300cd872 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -155,7 +155,7 @@ void GDScriptByteCodeGenerator::end_parameters() { function->default_arguments.reverse(); } -void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) { +void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) { function = memnew(GDScriptFunction); debug_stack = EngineDebugger::is_active(); @@ -209,8 +209,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { if (name_map.size()) { function->global_names.resize(name_map.size()); function->_global_names_ptr = &function->global_names[0]; - for (Map<StringName, int>::Element *E = name_map.front(); E; E = E->next()) { - function->global_names.write[E->get()] = E->key(); + for (const KeyValue<StringName, int> &E : name_map) { + function->global_names.write[E.value] = E.key; } function->_global_names_count = function->global_names.size(); @@ -241,8 +241,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->operator_funcs.resize(operator_func_map.size()); function->_operator_funcs_count = function->operator_funcs.size(); function->_operator_funcs_ptr = function->operator_funcs.ptr(); - for (const Map<Variant::ValidatedOperatorEvaluator, int>::Element *E = operator_func_map.front(); E; E = E->next()) { - function->operator_funcs.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedOperatorEvaluator, int> &E : operator_func_map) { + function->operator_funcs.write[E.value] = E.key; } } else { function->_operator_funcs_count = 0; @@ -253,8 +253,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->setters.resize(setters_map.size()); function->_setters_count = function->setters.size(); function->_setters_ptr = function->setters.ptr(); - for (const Map<Variant::ValidatedSetter, int>::Element *E = setters_map.front(); E; E = E->next()) { - function->setters.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedSetter, int> &E : setters_map) { + function->setters.write[E.value] = E.key; } } else { function->_setters_count = 0; @@ -265,8 +265,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->getters.resize(getters_map.size()); function->_getters_count = function->getters.size(); function->_getters_ptr = function->getters.ptr(); - for (const Map<Variant::ValidatedGetter, int>::Element *E = getters_map.front(); E; E = E->next()) { - function->getters.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedGetter, int> &E : getters_map) { + function->getters.write[E.value] = E.key; } } else { function->_getters_count = 0; @@ -277,8 +277,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->keyed_setters.resize(keyed_setters_map.size()); function->_keyed_setters_count = function->keyed_setters.size(); function->_keyed_setters_ptr = function->keyed_setters.ptr(); - for (const Map<Variant::ValidatedKeyedSetter, int>::Element *E = keyed_setters_map.front(); E; E = E->next()) { - function->keyed_setters.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedKeyedSetter, int> &E : keyed_setters_map) { + function->keyed_setters.write[E.value] = E.key; } } else { function->_keyed_setters_count = 0; @@ -289,8 +289,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->keyed_getters.resize(keyed_getters_map.size()); function->_keyed_getters_count = function->keyed_getters.size(); function->_keyed_getters_ptr = function->keyed_getters.ptr(); - for (const Map<Variant::ValidatedKeyedGetter, int>::Element *E = keyed_getters_map.front(); E; E = E->next()) { - function->keyed_getters.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedKeyedGetter, int> &E : keyed_getters_map) { + function->keyed_getters.write[E.value] = E.key; } } else { function->_keyed_getters_count = 0; @@ -301,8 +301,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->indexed_setters.resize(indexed_setters_map.size()); function->_indexed_setters_count = function->indexed_setters.size(); function->_indexed_setters_ptr = function->indexed_setters.ptr(); - for (const Map<Variant::ValidatedIndexedSetter, int>::Element *E = indexed_setters_map.front(); E; E = E->next()) { - function->indexed_setters.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedIndexedSetter, int> &E : indexed_setters_map) { + function->indexed_setters.write[E.value] = E.key; } } else { function->_indexed_setters_count = 0; @@ -313,8 +313,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->indexed_getters.resize(indexed_getters_map.size()); function->_indexed_getters_count = function->indexed_getters.size(); function->_indexed_getters_ptr = function->indexed_getters.ptr(); - for (const Map<Variant::ValidatedIndexedGetter, int>::Element *E = indexed_getters_map.front(); E; E = E->next()) { - function->indexed_getters.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedIndexedGetter, int> &E : indexed_getters_map) { + function->indexed_getters.write[E.value] = E.key; } } else { function->_indexed_getters_count = 0; @@ -325,8 +325,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->builtin_methods.resize(builtin_method_map.size()); function->_builtin_methods_ptr = function->builtin_methods.ptr(); function->_builtin_methods_count = builtin_method_map.size(); - for (const Map<Variant::ValidatedBuiltInMethod, int>::Element *E = builtin_method_map.front(); E; E = E->next()) { - function->builtin_methods.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedBuiltInMethod, int> &E : builtin_method_map) { + function->builtin_methods.write[E.value] = E.key; } } else { function->_builtin_methods_ptr = nullptr; @@ -337,8 +337,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->constructors.resize(constructors_map.size()); function->_constructors_ptr = function->constructors.ptr(); function->_constructors_count = constructors_map.size(); - for (const Map<Variant::ValidatedConstructor, int>::Element *E = constructors_map.front(); E; E = E->next()) { - function->constructors.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedConstructor, int> &E : constructors_map) { + function->constructors.write[E.value] = E.key; } } else { function->_constructors_ptr = nullptr; @@ -349,8 +349,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->utilities.resize(utilities_map.size()); function->_utilities_ptr = function->utilities.ptr(); function->_utilities_count = utilities_map.size(); - for (const Map<Variant::ValidatedUtilityFunction, int>::Element *E = utilities_map.front(); E; E = E->next()) { - function->utilities.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedUtilityFunction, int> &E : utilities_map) { + function->utilities.write[E.value] = E.key; } } else { function->_utilities_ptr = nullptr; @@ -361,8 +361,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->gds_utilities.resize(gds_utilities_map.size()); function->_gds_utilities_ptr = function->gds_utilities.ptr(); function->_gds_utilities_count = gds_utilities_map.size(); - for (const Map<GDScriptUtilityFunctions::FunctionPtr, int>::Element *E = gds_utilities_map.front(); E; E = E->next()) { - function->gds_utilities.write[E->get()] = E->key(); + for (const KeyValue<GDScriptUtilityFunctions::FunctionPtr, int> &E : gds_utilities_map) { + function->gds_utilities.write[E.value] = E.key; } } else { function->_gds_utilities_ptr = nullptr; @@ -373,8 +373,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->methods.resize(method_bind_map.size()); function->_methods_ptr = function->methods.ptrw(); function->_methods_count = method_bind_map.size(); - for (const Map<MethodBind *, int>::Element *E = method_bind_map.front(); E; E = E->next()) { - function->methods.write[E->get()] = E->key(); + for (const KeyValue<MethodBind *, int> &E : method_bind_map) { + function->methods.write[E.value] = E.key; } } else { function->_methods_ptr = nullptr; @@ -385,8 +385,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->lambdas.resize(lambdas_map.size()); function->_lambdas_ptr = function->lambdas.ptrw(); function->_lambdas_count = lambdas_map.size(); - for (const Map<GDScriptFunction *, int>::Element *E = lambdas_map.front(); E; E = E->next()) { - function->lambdas.write[E->get()] = E->key(); + for (const KeyValue<GDScriptFunction *, int> &E : lambdas_map) { + function->lambdas.write[E.value] = E.key; } } else { function->_lambdas_ptr = nullptr; @@ -864,6 +864,12 @@ void GDScriptByteCodeGenerator::write_assign_default_parameter(const Address &p_ function->default_arguments.push_back(opcodes.size()); } +void GDScriptByteCodeGenerator::write_store_global(const Address &p_dst, int p_global_index) { + append(GDScriptFunction::OPCODE_STORE_GLOBAL, 1); + append(p_dst); + append(p_global_index); +} + void GDScriptByteCodeGenerator::write_store_named_global(const Address &p_dst, const StringName &p_global) { append(GDScriptFunction::OPCODE_STORE_NAMED_GLOBAL, 1); append(p_dst); diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 3d6fb291ad..fbbf5802fd 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -153,12 +153,12 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { #endif locals.resize(current_locals); if (debug_stack) { - for (Map<StringName, int>::Element *E = block_identifiers.front(); E; E = E->next()) { + for (const KeyValue<StringName, int> &E : block_identifiers) { GDScriptFunction::StackDebug sd; sd.added = false; - sd.identifier = E->key(); + sd.identifier = E.key; sd.line = current_line; - sd.pos = E->get(); + sd.pos = E.value; stack_debug.push_back(sd); } block_identifiers = block_identifier_stack.back()->get(); @@ -419,7 +419,7 @@ public: virtual void start_block() override; virtual void end_block() override; - virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) override; + virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) override; virtual GDScriptFunction *write_end() override; #ifdef DEBUG_ENABLED @@ -454,6 +454,7 @@ public: virtual void write_assign_true(const Address &p_target) override; virtual void write_assign_false(const Address &p_target) override; virtual void write_assign_default_parameter(const Address &p_dst, const Address &p_src) override; + virtual void write_store_global(const Address &p_dst, int p_global_index) override; virtual void write_store_named_global(const Address &p_dst, const StringName &p_global) override; virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) override; virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 1a844bf241..8121053245 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -51,7 +51,9 @@ GDScriptParser *GDScriptParserRef::get_parser() const { Error GDScriptParserRef::raise_status(Status p_new_status) { ERR_FAIL_COND_V(parser == nullptr, ERR_INVALID_DATA); - Error result = OK; + if (result != OK) { + return result; + } while (p_new_status > status) { switch (status) { @@ -86,14 +88,6 @@ Error GDScriptParserRef::raise_status(Status p_new_status) { } } if (result != OK) { - if (parser != nullptr) { - memdelete(parser); - parser = nullptr; - } - if (analyzer != nullptr) { - memdelete(analyzer); - analyzer = nullptr; - } return result; } } @@ -200,7 +194,9 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro if (singleton->full_gdscript_cache.has(p_path)) { return singleton->full_gdscript_cache[p_path]; } + Ref<GDScript> script = get_shallow_script(p_path); + ERR_FAIL_COND_V(script.is_null(), Ref<GDScript>()); r_error = script->load_source_code(p_path); diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index 943638d29f..9fb661d031 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -54,6 +54,7 @@ private: GDScriptParser *parser = nullptr; GDScriptAnalyzer *analyzer = nullptr; Status status = EMPTY; + Error result = OK; String path; friend class GDScriptCache; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index ecc86c37f3..e6ecc92d55 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -31,7 +31,7 @@ #ifndef GDSCRIPT_CODEGEN #define GDSCRIPT_CODEGEN -#include "core/io/multiplayer_api.h" +#include "core/multiplayer/multiplayer.h" #include "core/string/string_name.h" #include "core/variant/variant.h" #include "gdscript_function.h" @@ -80,7 +80,7 @@ public: virtual void start_block() = 0; virtual void end_block() = 0; - virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) = 0; + virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) = 0; virtual GDScriptFunction *write_end() = 0; #ifdef DEBUG_ENABLED @@ -115,6 +115,7 @@ public: virtual void write_assign_true(const Address &p_target) = 0; virtual void write_assign_false(const Address &p_target) = 0; virtual void write_assign_default_parameter(const Address &dst, const Address &src) = 0; + virtual void write_store_global(const Address &p_dst, int p_global_index) = 0; virtual void write_store_named_global(const Address &p_dst, const StringName &p_global) = 0; virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) = 0; virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index fe827a5b72..9eee0b57f3 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -35,6 +35,8 @@ #include "gdscript_cache.h" #include "gdscript_utility_functions.h" +#include "core/config/project_settings.h" + bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringName &p_name) { if (codegen.function_node && codegen.function_node->is_static) { return false; @@ -126,8 +128,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D names.pop_back(); } result.kind = GDScriptDataType::GDSCRIPT; - result.script_type_ref = script; - result.script_type = result.script_type_ref.ptr(); + result.script_type = script.ptr(); result.native_type = script->get_instance_base_type(); } else { result.kind = GDScriptDataType::GDSCRIPT; @@ -317,10 +318,21 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } } + // Try globals. if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { - int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; - Variant global = GDScriptLanguage::get_singleton()->get_global_array()[idx]; - return codegen.add_constant(global); // TODO: Get type. + // If it's an autoload singleton, we postpone to load it at runtime. + // This is so one autoload doesn't try to load another before it's compiled. + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + if (autoloads.has(identifier) && autoloads[identifier].is_singleton) { + GDScriptCodeGenerator::Address global = codegen.add_temporary(_gdtype_from_datatype(in->get_datatype())); + int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; + gen->write_store_global(global, idx); + return global; + } else { + int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; + Variant global = GDScriptLanguage::get_singleton()->get_global_array()[idx]; + return codegen.add_constant(global); + } } // Try global classes. @@ -428,7 +440,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code break; case GDScriptParser::DictionaryNode::LUA_TABLE: // Lua-style: key is an identifier interpreted as StringName. - StringName key = static_cast<const GDScriptParser::IdentifierNode *>(dn->elements[i].key)->name; + StringName key = dn->elements[i].key->reduced_value.operator StringName(); element = codegen.add_constant(key); break; } @@ -952,7 +964,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Perform operator if any. if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { - GDScriptCodeGenerator::Address value = codegen.add_temporary(); + GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype())); if (subscript->is_attribute) { gen->write_get_named(value, name, prev_base); } else { @@ -1007,25 +1019,32 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } } else if (assignment->assignee->type == GDScriptParser::Node::IDENTIFIER && _is_class_member_property(codegen, static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name)) { // Assignment to member property. - GDScriptCodeGenerator::Address assigned = _parse_expression(codegen, r_error, assignment->assigned_value); + GDScriptCodeGenerator::Address assigned_value = _parse_expression(codegen, r_error, assignment->assigned_value); if (r_error) { return GDScriptCodeGenerator::Address(); } - GDScriptCodeGenerator::Address assign_temp = assigned; + + GDScriptCodeGenerator::Address to_assign = assigned_value; + bool has_operation = assignment->operation != GDScriptParser::AssignmentNode::OP_NONE; StringName name = static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name; - if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { + if (has_operation) { + GDScriptCodeGenerator::Address op_result = codegen.add_temporary(); GDScriptCodeGenerator::Address member = codegen.add_temporary(); gen->write_get_member(member, name); - gen->write_binary_operator(assigned, assignment->variant_op, member, assigned); - gen->pop_temporary(); + gen->write_binary_operator(op_result, assignment->variant_op, member, assigned_value); + gen->pop_temporary(); // Pop member temp. + to_assign = op_result; } - gen->write_set_member(assigned, name); + gen->write_set_member(to_assign, name); - if (assign_temp.mode == GDScriptCodeGenerator::Address::TEMPORARY) { - gen->pop_temporary(); + if (to_assign.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); // Pop the assigned expression or the temp result if it has operation. + } + if (has_operation && assigned_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); // Pop the assigned expression if not done before. } } else { // Regular assignment. @@ -1059,19 +1078,25 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } } - GDScriptCodeGenerator::Address assigned = _parse_expression(codegen, r_error, assignment->assigned_value); - GDScriptCodeGenerator::Address op_result; + GDScriptCodeGenerator::Address assigned_value = _parse_expression(codegen, r_error, assignment->assigned_value); if (r_error) { return GDScriptCodeGenerator::Address(); } - if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { + GDScriptCodeGenerator::Address to_assign; + bool has_operation = assignment->operation != GDScriptParser::AssignmentNode::OP_NONE; + if (has_operation) { // Perform operation. - op_result = codegen.add_temporary(); - gen->write_binary_operator(op_result, assignment->variant_op, target, assigned); + GDScriptCodeGenerator::Address op_result = codegen.add_temporary(); + GDScriptCodeGenerator::Address og_value = _parse_expression(codegen, r_error, assignment->assignee); + gen->write_binary_operator(op_result, assignment->variant_op, og_value, assigned_value); + to_assign = op_result; + + if (og_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } } else { - op_result = assigned; - assigned = GDScriptCodeGenerator::Address(); + to_assign = assigned_value; } GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype()); @@ -1079,25 +1104,25 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code if (has_setter && !is_in_setter) { // Call setter. Vector<GDScriptCodeGenerator::Address> args; - args.push_back(op_result); + args.push_back(to_assign); gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), setter_function, args); } else { // Just assign. if (assignment->use_conversion_assign) { - gen->write_assign_with_conversion(target, op_result); + gen->write_assign_with_conversion(target, to_assign); } else { - gen->write_assign(target, op_result); + gen->write_assign(target, to_assign); } } - if (op_result.mode == GDScriptCodeGenerator::Address::TEMPORARY) { - gen->pop_temporary(); + if (to_assign.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); // Pop assigned value or temp operation result. } - if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { - gen->pop_temporary(); + if (has_operation && assigned_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); // Pop assigned value if not done before. } if (target.mode == GDScriptCodeGenerator::Address::TEMPORARY) { - gen->pop_temporary(); + gen->pop_temporary(); // Pop the target to assignment. } } return GDScriptCodeGenerator::Address(); // Assignment does not return a value. @@ -1858,7 +1883,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ StringName func_name; bool is_static = false; - MultiplayerAPI::RPCConfig rpc_config; + Multiplayer::RPCConfig rpc_config; GDScriptDataType return_type; return_type.has_type = true; return_type.kind = GDScriptDataType::BUILTIN; @@ -2087,7 +2112,7 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP return_type = _gdtype_from_datatype(p_variable->get_datatype(), p_script); } - codegen.generator->write_start(p_script, func_name, false, MultiplayerAPI::RPCConfig(), return_type); + codegen.generator->write_start(p_script, func_name, false, Multiplayer::RPCConfig(), return_type); if (p_is_setter) { uint32_t par_addr = codegen.generator->add_parameter(p_variable->setter_parameter->name, false, _gdtype_from_datatype(p_variable->get_datatype())); @@ -2175,8 +2200,8 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->_base = nullptr; p_script->members.clear(); p_script->constants.clear(); - for (Map<StringName, GDScriptFunction *>::Element *E = p_script->member_functions.front(); E; E = E->next()) { - memdelete(E->get()); + for (const KeyValue<StringName, GDScriptFunction *> &E : p_script->member_functions) { + memdelete(E.value); } p_script->member_functions.clear(); p_script->member_indices.clear(); @@ -2187,6 +2212,13 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->tool = parser->is_tool(); p_script->name = p_class->identifier ? p_class->identifier->name : ""; + if (p_script->name != "") { + if (ClassDB::class_exists(p_script->name) && ClassDB::is_class_exposed(p_script->name)) { + _set_error("The class '" + p_script->name + "' shadows a native class", p_class); + return ERR_ALREADY_EXISTS; + } + } + Ref<GDScriptNativeClass> native; GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type); @@ -2338,28 +2370,6 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar const GDScriptParser::SignalNode *signal = member.signal; StringName name = signal->identifier->name; - GDScript *c = p_script; - - while (c) { - if (c->_signals.has(name)) { - _set_error("Signal '" + name + "' redefined (in current or parent class)", p_class); - return ERR_ALREADY_EXISTS; - } - - if (c->base.is_valid()) { - c = c->base.ptr(); - } else { - c = nullptr; - } - } - - if (native.is_valid()) { - if (ClassDB::has_signal(native->get_name(), name)) { - _set_error("Signal '" + name + "' redefined (original in native class '" + String(native->get_name()) + "')", p_class); - return ERR_ALREADY_EXISTS; - } - } - Vector<StringName> parameters_names; parameters_names.resize(signal->parameters.size()); for (int j = 0; j < signal->parameters.size(); j++) { @@ -2516,8 +2526,8 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa instance->owner = E->get(); //needed for hot reloading - for (Map<StringName, GDScript::MemberInfo>::Element *F = p_script->member_indices.front(); F; F = F->next()) { - instance->member_indices_cache[F->key()] = F->get().index; + for (const KeyValue<StringName, GDScript::MemberInfo> &F : p_script->member_indices) { + instance->member_indices_cache[F.key] = F.value.index; } instance->owner->set_script_instance(instance); diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index 1acb9ceddc..9287df2ea0 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -914,6 +914,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 5; } break; DISASSEMBLE_ITERATE_TYPES(DISASSEMBLE_ITERATE); + case OPCODE_STORE_GLOBAL: { + text += "store global "; + text += DADDR(1); + text += " = "; + text += String::num_int64(_code_ptr[ip + 2]); + + incr += 3; + } break; case OPCODE_STORE_NAMED_GLOBAL: { text += "store named global "; text += DADDR(1); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 2e570d5a5b..e49bf518a2 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -173,8 +173,8 @@ bool GDScriptLanguage::validate(const String &p_script, const String &p_path, Li get_function_names_recursively(cl, "", funcs); - for (Map<int, String>::Element *E = funcs.front(); E; E = E->next()) { - r_functions->push_back(E->get() + ":" + itos(E->key())); + for (const KeyValue<int, String> &E : funcs) { + r_functions->push_back(E.value + ":" + itos(E.key)); } } @@ -344,9 +344,9 @@ void GDScriptLanguage::debug_get_stack_level_members(int p_level, List<String> * const Map<StringName, GDScript::MemberInfo> &mi = script->debug_get_member_indices(); - for (const Map<StringName, GDScript::MemberInfo>::Element *E = mi.front(); E; E = E->next()) { - p_members->push_back(E->key()); - p_values->push_back(instance->debug_get_member_by_index(E->get().index)); + for (const KeyValue<StringName, GDScript::MemberInfo> &E : mi) { + p_members->push_back(E.key); + p_values->push_back(instance->debug_get_member_by_index(E.value.index)); } } @@ -370,14 +370,14 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> List<Pair<String, Variant>> cinfo; get_public_constants(&cinfo); - for (const Map<StringName, int>::Element *E = name_idx.front(); E; E = E->next()) { - if (ClassDB::class_exists(E->key()) || Engine::get_singleton()->has_singleton(E->key())) { + for (const KeyValue<StringName, int> &E : name_idx) { + if (ClassDB::class_exists(E.key) || Engine::get_singleton()->has_singleton(E.key)) { continue; } bool is_script_constant = false; for (List<Pair<String, Variant>>::Element *CE = cinfo.front(); CE; CE = CE->next()) { - if (CE->get().first == E->key()) { + if (CE->get().first == E.key) { is_script_constant = true; break; } @@ -386,7 +386,7 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> continue; } - const Variant &var = globals[E->value()]; + const Variant &var = globals[E.value]; if (Object *obj = var) { if (Object::cast_to<GDScriptNativeClass>(obj)) { continue; @@ -395,7 +395,7 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> bool skip = false; for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) { - if (E->key() == CoreConstants::get_global_constant_name(i)) { + if (E.key == CoreConstants::get_global_constant_name(i)) { skip = true; break; } @@ -404,7 +404,7 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> continue; } - p_globals->push_back(E->key()); + p_globals->push_back(E.key); p_values->push_back(var); } } @@ -623,11 +623,11 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio } static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Map<String, ScriptCodeCompletionOption> &r_list) { - const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; + const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; for (int i = 0; i < p_dir->get_file_count(); i++) { ScriptCodeCompletionOption option(p_dir->get_file_path(i), ScriptCodeCompletionOption::KIND_FILE_PATH); - option.insert_text = quote_style + option.display + quote_style; + option.insert_text = option.display.quote(quote_style); r_list.insert(option.display, option); } @@ -637,24 +637,24 @@ static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Map<String } static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_annotation, int p_argument, const String p_quote_style, Map<String, ScriptCodeCompletionOption> &r_result) { - if (p_annotation->name == "@export_range" || p_annotation->name == "@export_exp_range") { + if (p_annotation->name == "@export_range") { if (p_argument == 3 || p_argument == 4) { // Slider hint. ScriptCodeCompletionOption slider1("or_greater", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); - slider1.insert_text = p_quote_style + slider1.display + p_quote_style; + slider1.insert_text = slider1.display.quote(p_quote_style); r_result.insert(slider1.display, slider1); ScriptCodeCompletionOption slider2("or_lesser", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); - slider2.insert_text = p_quote_style + slider2.display + p_quote_style; + slider2.insert_text = slider2.display.quote(p_quote_style); r_result.insert(slider2.display, slider2); } } else if (p_annotation->name == "@export_exp_easing") { if (p_argument == 0 || p_argument == 1) { // Easing hint. ScriptCodeCompletionOption hint1("attenuation", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); - hint1.insert_text = p_quote_style + hint1.display + p_quote_style; + hint1.insert_text = hint1.display.quote(p_quote_style); r_result.insert(hint1.display, hint1); ScriptCodeCompletionOption hint2("inout", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); - hint2.insert_text = p_quote_style + hint2.display + p_quote_style; + hint2.insert_text = hint2.display.quote(p_quote_style); r_result.insert(hint2.display, hint2); } } else if (p_annotation->name == "@export_node_path") { @@ -731,9 +731,9 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio } // Autoload singletons - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (const Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E != nullptr; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->get(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.get(); if (!info.is_singleton || info.path.get_extension().to_lower() != "gd") { continue; } @@ -781,6 +781,9 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, if (p_only_functions) { continue; } + if (r_result.has(member.constant->identifier->name)) { + continue; + } option = ScriptCodeCompletionOption(member.constant->identifier->name, ScriptCodeCompletionOption::KIND_CONSTANT); if (member.constant->initializer) { option.default_value = member.constant->initializer->reduced_value; @@ -872,8 +875,8 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base } Map<StringName, Variant> constants; scr->get_constants(&constants); - for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); + for (const KeyValue<StringName, Variant> &E : constants) { + ScriptCodeCompletionOption option(E.key.operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); r_result.insert(option.display, option); } @@ -912,7 +915,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base } } break; case GDScriptParser::DataType::NATIVE: { - StringName type = GDScriptParser::get_real_class_name(base_type.native_type); + StringName type = base_type.native_type; if (!ClassDB::class_exists(type)) { return; } @@ -1086,22 +1089,22 @@ static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool kwa++; } - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (const Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E != nullptr; E = E->next()) { - if (!E->value().is_singleton) { + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + if (!E.value().is_singleton) { continue; } - ScriptCodeCompletionOption option(E->key(), ScriptCodeCompletionOption::KIND_CONSTANT); + ScriptCodeCompletionOption option(E.key(), ScriptCodeCompletionOption::KIND_CONSTANT); r_result.insert(option.display, option); } // Native classes and global constants. - for (const Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) { + for (const KeyValue<StringName, int> &E : GDScriptLanguage::get_singleton()->get_global_map()) { ScriptCodeCompletionOption option; - if (ClassDB::class_exists(E->key()) || Engine::get_singleton()->has_singleton(E->key())) { - option = ScriptCodeCompletionOption(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); + if (ClassDB::class_exists(E.key) || Engine::get_singleton()->has_singleton(E.key)) { + option = ScriptCodeCompletionOption(E.key.operator String(), ScriptCodeCompletionOption::KIND_CLASS); } else { - option = ScriptCodeCompletionOption(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); + option = ScriptCodeCompletionOption(E.key.operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); } r_result.insert(option.display, option); } @@ -1326,10 +1329,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, native_type.kind = GDScriptParser::DataType::NATIVE; native_type.native_type = native_type.script_type->get_instance_base_type(); if (!ClassDB::class_exists(native_type.native_type)) { - native_type.native_type = String("_") + native_type.native_type; - if (!ClassDB::class_exists(native_type.native_type)) { - native_type.kind = GDScriptParser::DataType::UNRESOLVED; - } + native_type.kind = GDScriptParser::DataType::UNRESOLVED; } } } @@ -1362,12 +1362,12 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which]); found = true; } else { - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - String name = E->key(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + String name = E.key(); if (name == which) { - String script = E->value().path; + String script = E.value().path; if (!script.begins_with("res://")) { script = "res://" + script; @@ -1736,7 +1736,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } } - if (is_function_parameter && p_context.current_function && p_context.current_class) { + if (is_function_parameter && p_context.current_function && p_context.current_function->source_lambda == nullptr && p_context.current_class) { // Check if it's override of native function, then we can assume the type from the signature. GDScriptParser::DataType base_type = p_context.current_class->base_type; while (base_type.is_set()) { @@ -1765,9 +1765,8 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, base_type = GDScriptParser::DataType(); break; } - StringName real_native = GDScriptParser::get_real_class_name(base_type.native_type); MethodInfo info; - if (ClassDB::get_method_info(real_native, p_context.current_function->identifier->name, &info)) { + if (ClassDB::get_method_info(base_type.native_type, p_context.current_function->identifier->name, &info)) { for (const PropertyInfo &E : info.arguments) { if (E.name == p_identifier) { r_type = _type_from_property(E); @@ -1836,13 +1835,12 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } // Check ClassDB. - StringName class_name = GDScriptParser::get_real_class_name(p_identifier); - if (ClassDB::class_exists(class_name) && ClassDB::is_class_exposed(class_name)) { + if (ClassDB::class_exists(p_identifier) && ClassDB::is_class_exposed(p_identifier)) { r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.kind = GDScriptParser::DataType::NATIVE; r_type.type.native_type = p_identifier; r_type.type.is_constant = true; - r_type.type.is_meta_type = !Engine::get_singleton()->has_singleton(class_name); + r_type.type.is_meta_type = !Engine::get_singleton()->has_singleton(p_identifier); r_type.value = Variant(); } @@ -1951,7 +1949,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & } } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type); + StringName class_name = base_type.native_type; if (!ClassDB::class_exists(class_name)) { return false; } @@ -2113,11 +2111,10 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex } } break; case GDScriptParser::DataType::NATIVE: { - StringName native = GDScriptParser::get_real_class_name(base_type.native_type); - if (!ClassDB::class_exists(native)) { + if (!ClassDB::class_exists(base_type.native_type)) { return false; } - MethodBind *mb = ClassDB::get_method(native, p_method); + MethodBind *mb = ClassDB::get_method(base_type.native_type, p_method); if (mb) { r_type = _type_from_property(mb->get_return_info()); return true; @@ -2192,7 +2189,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c Variant base = p_base.value; GDScriptParser::DataType base_type = p_base.type; - const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; + const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; while (base_type.is_set() && !base_type.is_variant()) { switch (base_type.kind) { @@ -2209,7 +2206,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c base_type = base_type.class_type->base_type; } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type); + StringName class_name = base_type.native_type; if (!ClassDB::class_exists(class_name)) { base_type.kind = GDScriptParser::DataType::UNRESOLVED; break; @@ -2225,8 +2222,11 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c if (obj) { List<String> options; obj->get_argument_options(p_method, p_argidx, &options); - for (const String &F : options) { - ScriptCodeCompletionOption option(F, ScriptCodeCompletionOption::KIND_FUNCTION); + for (String &opt : options) { + if (opt.is_quoted()) { + opt = opt.unquote().quote(quote_style); // Handle user preference. + } + ScriptCodeCompletionOption option(opt, ScriptCodeCompletionOption::KIND_FUNCTION); r_result.insert(option.display, option); } } @@ -2254,7 +2254,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } String name = s.get_slice("/", 1); ScriptCodeCompletionOption option("/root/" + name, ScriptCodeCompletionOption::KIND_NODE_PATH); - option.insert_text = quote_style + option.display + quote_style; + option.insert_text = option.display.quote(quote_style); r_result.insert(option.display, option); } } @@ -2270,7 +2270,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } String name = s.get_slice("/", 1); ScriptCodeCompletionOption option(name, ScriptCodeCompletionOption::KIND_CONSTANT); - option.insert_text = quote_style + option.display + quote_style; + option.insert_text = option.display.quote(quote_style); r_result.insert(option.display, option); } } @@ -2305,8 +2305,6 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, const GDScriptParser::Node *p_call, int p_argidx, Map<String, ScriptCodeCompletionOption> &r_result, bool &r_forced, String &r_arghint) { - const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; - if (p_call->type == GDScriptParser::Node::PRELOAD) { if (p_argidx == 0 && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) { _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), r_result); @@ -2382,7 +2380,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } ::Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) { - const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; + const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; GDScriptParser parser; GDScriptAnalyzer analyzer(&parser); @@ -2594,7 +2592,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c break; } - StringName class_name = GDScriptParser::get_real_class_name(native_type.native_type); + StringName class_name = native_type.native_type; if (!ClassDB::class_exists(class_name)) { break; } @@ -2648,31 +2646,34 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } } break; case GDScriptParser::COMPLETION_GET_NODE: { + // Handles the `$Node/Path` or `$"Some NodePath"` syntax specifically. if (p_owner) { List<String> opts; p_owner->get_argument_options("get_node", 0, &opts); for (const String &E : opts) { + r_forced = true; String opt = E.strip_edges(); if (opt.is_quoted()) { - r_forced = true; - String idopt = opt.unquote(); - if (idopt.replace("/", "_").is_valid_identifier()) { - ScriptCodeCompletionOption option(idopt, ScriptCodeCompletionOption::KIND_NODE_PATH); - options.insert(option.display, option); - } else { - ScriptCodeCompletionOption option(opt, ScriptCodeCompletionOption::KIND_NODE_PATH); - options.insert(option.display, option); - } + // Remove quotes so that we can handle user preferred quote style, + // or handle NodePaths which are valid identifiers and don't need quotes. + opt = opt.unquote(); + } + // The path needs quotes if it's not a valid identifier (with an exception + // for "/" as path separator, which also doesn't require quotes). + if (!opt.replace("/", "_").is_valid_identifier()) { + opt = opt.quote(quote_style); // Handle user preference. } + ScriptCodeCompletionOption option(opt, ScriptCodeCompletionOption::KIND_NODE_PATH); + options.insert(option.display, option); } // Get autoloads. - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - String name = E->key(); - ScriptCodeCompletionOption option(quote_style + "/root/" + name + quote_style, ScriptCodeCompletionOption::KIND_NODE_PATH); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + String path = "/root/" + E.key(); + ScriptCodeCompletionOption option(path.quote(quote_style), ScriptCodeCompletionOption::KIND_NODE_PATH); options.insert(option.display, option); } } @@ -2685,8 +2686,8 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } break; } - for (Map<String, ScriptCodeCompletionOption>::Element *E = options.front(); E; E = E->next()) { - r_options->push_back(E->get()); + for (const KeyValue<String, ScriptCodeCompletionOption> &E : options) { + r_options->push_back(E.value); } return OK; @@ -2705,10 +2706,10 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path String GDScriptLanguage::_get_indentation() const { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { - bool use_space_indentation = EDITOR_DEF("text_editor/indent/type", false); + bool use_space_indentation = EDITOR_DEF("text_editor/behavior/indent/type", false); if (use_space_indentation) { - int indent_size = EDITOR_DEF("text_editor/indent/size", 4); + int indent_size = EDITOR_DEF("text_editor/behavior/indent/size", 4); String space_indent = ""; for (int i = 0; i < indent_size; i++) { @@ -2823,7 +2824,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type); + StringName class_name = base_type.native_type; if (!ClassDB::class_exists(class_name)) { base_type.kind = GDScriptParser::DataType::UNRESOLVED; break; @@ -2875,11 +2876,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co StringName parent = ClassDB::get_parent_class(class_name); if (parent != StringName()) { - if (String(parent).begins_with("_")) { - base_type.native_type = String(parent).substr(1); - } else { - base_type.native_type = parent; - } + base_type.native_type = parent; } else { base_type.kind = GDScriptParser::DataType::UNRESOLVED; } @@ -2933,18 +2930,11 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } ::Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) { - //before parsing, try the usual stuff + // Before parsing, try the usual stuff if (ClassDB::class_exists(p_symbol)) { r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS; r_result.class_name = p_symbol; return OK; - } else { - String under_prefix = "_" + p_symbol; - if (ClassDB::class_exists(under_prefix)) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS; - r_result.class_name = p_symbol; - return OK; - } } for (int i = 0; i < Variant::VARIANT_MAX; i++) { @@ -3097,6 +3087,15 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co r_result.class_member = p_symbol; return OK; } + } else { + List<StringName> utility_functions; + Variant::get_utility_function_list(&utility_functions); + if (utility_functions.find(p_symbol) != nullptr) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_TBD_GLOBALSCOPE; + r_result.class_name = "@GlobalScope"; + r_result.class_member = p_symbol; + return OK; + } } } } break; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 876c508689..a3f0c7dfef 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -120,11 +120,11 @@ void GDScriptFunction::debug_get_stack_member_state(int p_line, List<Pair<String } List<_GDFKCS> stackpositions; - for (Map<StringName, _GDFKC>::Element *E = sdmap.front(); E; E = E->next()) { + for (const KeyValue<StringName, _GDFKC> &E : sdmap) { _GDFKCS spp; - spp.id = E->key(); - spp.order = E->get().order; - spp.pos = E->get().pos.back()->get(); + spp.id = E.key; + spp.order = E.value.order; + spp.pos = E.value.pos.back()->get(); stackpositions.push_back(spp); } diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 9e5ef0f632..9d076a8e4c 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -111,11 +111,7 @@ public: } if (!ClassDB::is_parent_class(obj->get_class_name(), native_type)) { - // Try with underscore prefix - StringName underscore_native_type = "_" + native_type; - if (!ClassDB::is_parent_class(obj->get_class_name(), underscore_native_type)) { - return false; - } + return false; } return true; } break; @@ -352,6 +348,7 @@ public: OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, OPCODE_ITERATE_PACKED_COLOR_ARRAY, OPCODE_ITERATE_OBJECT, + OPCODE_STORE_GLOBAL, OPCODE_STORE_NAMED_GLOBAL, OPCODE_TYPE_ADJUST_BOOL, OPCODE_TYPE_ADJUST_INT, @@ -472,7 +469,7 @@ private: int _initial_line = 0; bool _static = false; - MultiplayerAPI::RPCConfig rpc_config; + Multiplayer::RPCConfig rpc_config; GDScript *_script = nullptr; @@ -592,7 +589,7 @@ public: void disassemble(const Vector<String> &p_code_lines) const; #endif - _FORCE_INLINE_ MultiplayerAPI::RPCConfig get_rpc_config() const { return rpc_config; } + _FORCE_INLINE_ Multiplayer::RPCConfig get_rpc_config() const { return rpc_config; } GDScriptFunction(); ~GDScriptFunction(); }; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index a21167ad95..ad75e8174c 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -94,43 +94,8 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { return Variant::VARIANT_MAX; } -// TODO: Move this to a central location (maybe core?). -static HashMap<StringName, StringName> underscore_map; -static const char *underscore_classes[] = { - "ClassDB", - "Directory", - "Engine", - "File", - "Geometry", - "GodotSharp", - "JSON", - "Marshalls", - "Mutex", - "OS", - "ResourceLoader", - "ResourceSaver", - "Semaphore", - "Thread", - "VisualScriptEditor", - nullptr, -}; -StringName GDScriptParser::get_real_class_name(const StringName &p_source) { - if (underscore_map.is_empty()) { - const char **class_name = underscore_classes; - while (*class_name != nullptr) { - underscore_map[*class_name] = String("_") + *class_name; - class_name++; - } - } - if (underscore_map.has(p_source)) { - return underscore_map[p_source]; - } - return p_source; -} - void GDScriptParser::cleanup() { builtin_types.clear(); - underscore_map.clear(); } void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const { @@ -168,7 +133,7 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>); // Networking. - register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_PUPPET>, 4, true); + register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<Multiplayer::RPC_MODE_AUTHORITY>, 4, true); // TODO: Warning annotations. } @@ -337,7 +302,7 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ int tab_size = 4; #ifdef TOOLS_ENABLED if (EditorSettings::get_singleton()) { - tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size"); + tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size"); } #endif // TOOLS_ENABLED @@ -372,12 +337,29 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ tokenizer.set_cursor_position(cursor_line, cursor_column); script_path = p_script_path; current = tokenizer.scan(); - // Avoid error as the first token. - while (current.type == GDScriptTokenizer::Token::ERROR) { - push_error(current.literal); + // Avoid error or newline as the first token. + // The latter can mess with the parser when opening files filled exclusively with comments and newlines. + while (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::NEWLINE) { + if (current.type == GDScriptTokenizer::Token::ERROR) { + push_error(current.literal); + } current = tokenizer.scan(); } +#ifdef DEBUG_ENABLED + // Warn about parsing an empty script file: + if (current.type == GDScriptTokenizer::Token::TK_EOF) { + // Create a dummy Node for the warning, pointing to the very beginning of the file + Node *nd = alloc_node<PassNode>(); + nd->start_line = 1; + nd->start_column = 0; + nd->end_line = 1; + nd->leftmost_column = 0; + nd->rightmost_column = 0; + push_warning(nd, GDScriptWarning::EMPTY_FILE); + } +#endif + push_multiline(false); // Keep one for the whole parsing. parse_program(); pop_multiline(); @@ -597,12 +579,12 @@ void GDScriptParser::parse_program() { } } - parse_class_body(); + parse_class_body(true); #ifdef TOOLS_ENABLED - for (Map<int, GDScriptTokenizer::CommentData>::Element *E = tokenizer.get_comments().front(); E; E = E->next()) { - if (E->get().new_line && E->get().comment.begins_with("##")) { - class_doc_line = MIN(class_doc_line, E->key()); + for (const KeyValue<int, GDScriptTokenizer::CommentData> &E : tokenizer.get_comments()) { + if (E.value.new_line && E.value.comment.begins_with("##")) { + class_doc_line = MIN(class_doc_line, E.key); } } if (has_comment(class_doc_line)) { @@ -633,9 +615,10 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() { } consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after class declaration.)"); - consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected newline after class declaration.)"); - if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block after class declaration.)")) { + bool multiline = match(GDScriptTokenizer::Token::NEWLINE); + + if (multiline && !consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block after class declaration.)")) { current_class = previous_class; return n_class; } @@ -648,9 +631,11 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() { end_statement("superclass"); } - parse_class_body(); + parse_class_body(multiline); - consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)"); + if (multiline) { + consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)"); + } current_class = previous_class; return n_class; @@ -765,7 +750,7 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)() } } -void GDScriptParser::parse_class_body() { +void GDScriptParser::parse_class_body(bool p_is_multiline) { bool class_end = false; while (!class_end && !is_at_end()) { switch (current.type) { @@ -811,6 +796,9 @@ void GDScriptParser::parse_class_body() { if (panic_mode) { synchronize(); } + if (!p_is_multiline) { + class_end = true; + } } } @@ -1071,7 +1059,9 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() { SignalNode *signal = alloc_node<SignalNode>(); signal->identifier = parse_identifier(); - if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { + if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { + push_multiline(true); + advance(); do { if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { // Allow for trailing comma. @@ -1094,6 +1084,7 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() { } } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end()); + pop_multiline(); consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*"); } @@ -1152,7 +1143,6 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { } item.custom_value = value; } - item.rightmost_column = previous.rightmost_column; item.index = enum_node->values.size(); enum_node->values.push_back(item); @@ -1377,6 +1367,9 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, int error_count = 0; do { + if (!multiline && previous.type == GDScriptTokenizer::Token::SEMICOLON && check(GDScriptTokenizer::Token::NEWLINE)) { + break; + } Node *statement = parse_statement(); if (statement == nullptr) { if (error_count++ > 100) { @@ -1417,7 +1410,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, break; } - } while (multiline && !check(GDScriptTokenizer::Token::DEDENT) && !lambda_ended && !is_at_end()); + } while ((multiline || previous.type == GDScriptTokenizer::Token::SEMICOLON) && !check(GDScriptTokenizer::Token::DEDENT) && !lambda_ended && !is_at_end()); if (multiline) { if (!lambda_ended) { @@ -1627,6 +1620,10 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { n_for->list = parse_expression(false); + if (!n_for->list) { + push_error(R"(Expected a list or range after "in".)"); + } + consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "for" condition.)"); // Save break/continue state. @@ -1718,6 +1715,7 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() { while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) { MatchBranchNode *branch = parse_match_branch(); if (branch == nullptr) { + advance(); continue; } @@ -1781,7 +1779,9 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { push_error(R"(No pattern found for "match" branch.)"); } - consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)"); + if (!consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)")) { + return nullptr; + } // Save continue state. bool could_continue = can_continue; @@ -1814,15 +1814,6 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ PatternNode *pattern = alloc_node<PatternNode>(); switch (current.type) { - case GDScriptTokenizer::Token::LITERAL: - advance(); - pattern->pattern_type = PatternNode::PT_LITERAL; - pattern->literal = parse_literal(); - if (pattern->literal == nullptr) { - // Error happened. - return nullptr; - } - break; case GDScriptTokenizer::Token::VAR: { // Bind. advance(); @@ -1885,44 +1876,44 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ // Dictionary. advance(); pattern->pattern_type = PatternNode::PT_DICTIONARY; - - if (!check(GDScriptTokenizer::Token::BRACE_CLOSE) && !is_at_end()) { - do { - if (match(GDScriptTokenizer::Token::PERIOD_PERIOD)) { - // Rest. + do { + if (check(GDScriptTokenizer::Token::BRACE_CLOSE) || is_at_end()) { + break; + } + if (match(GDScriptTokenizer::Token::PERIOD_PERIOD)) { + // Rest. + if (pattern->rest_used) { + push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)"); + } else { + PatternNode *sub_pattern = alloc_node<PatternNode>(); + sub_pattern->pattern_type = PatternNode::PT_REST; + pattern->dictionary.push_back({ nullptr, sub_pattern }); + pattern->rest_used = true; + } + } else { + ExpressionNode *key = parse_expression(false); + if (key == nullptr) { + push_error(R"(Expected expression as key for dictionary pattern.)"); + } + if (match(GDScriptTokenizer::Token::COLON)) { + // Value pattern. + PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern); + if (sub_pattern == nullptr) { + continue; + } if (pattern->rest_used) { push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)"); + } else if (sub_pattern->pattern_type == PatternNode::PT_REST) { + push_error(R"(The ".." pattern cannot be used as a value.)"); } else { - PatternNode *sub_pattern = alloc_node<PatternNode>(); - sub_pattern->pattern_type = PatternNode::PT_REST; - pattern->dictionary.push_back({ nullptr, sub_pattern }); - pattern->rest_used = true; + pattern->dictionary.push_back({ key, sub_pattern }); } } else { - ExpressionNode *key = parse_expression(false); - if (key == nullptr) { - push_error(R"(Expected expression as key for dictionary pattern.)"); - } - if (match(GDScriptTokenizer::Token::COLON)) { - // Value pattern. - PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern); - if (sub_pattern == nullptr) { - continue; - } - if (pattern->rest_used) { - push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)"); - } else if (sub_pattern->pattern_type == PatternNode::PT_REST) { - push_error(R"(The ".." pattern cannot be used as a value.)"); - } else { - pattern->dictionary.push_back({ key, sub_pattern }); - } - } else { - // Key match only. - pattern->dictionary.push_back({ key, nullptr }); - } + // Key match only. + pattern->dictionary.push_back({ key, nullptr }); } - } while (match(GDScriptTokenizer::Token::COMMA)); - } + } + } while (match(GDScriptTokenizer::Token::COMMA)); consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected "}" to close the dictionary pattern.)"); break; } @@ -1931,8 +1922,13 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ ExpressionNode *expression = parse_expression(false); if (expression == nullptr) { push_error(R"(Expected expression for match pattern.)"); + return nullptr; } else { - pattern->pattern_type = PatternNode::PT_EXPRESSION; + if (expression->type == GDScriptParser::Node::LITERAL) { + pattern->pattern_type = PatternNode::PT_LITERAL; + } else { + pattern->pattern_type = PatternNode::PT_EXPRESSION; + } pattern->expression = expression; } break; @@ -2143,22 +2139,34 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_unary_operator(ExpressionN operation->operation = UnaryOpNode::OP_NEGATIVE; operation->variant_op = Variant::OP_NEGATE; operation->operand = parse_precedence(PREC_SIGN, false); + if (operation->operand == nullptr) { + push_error(R"(Expected expression after "-" operator.)"); + } break; case GDScriptTokenizer::Token::PLUS: operation->operation = UnaryOpNode::OP_POSITIVE; operation->variant_op = Variant::OP_POSITIVE; operation->operand = parse_precedence(PREC_SIGN, false); + if (operation->operand == nullptr) { + push_error(R"(Expected expression after "+" operator.)"); + } break; case GDScriptTokenizer::Token::TILDE: operation->operation = UnaryOpNode::OP_COMPLEMENT; operation->variant_op = Variant::OP_BIT_NEGATE; operation->operand = parse_precedence(PREC_BIT_NOT, false); + if (operation->operand == nullptr) { + push_error(R"(Expected expression after "~" operator.)"); + } break; case GDScriptTokenizer::Token::NOT: case GDScriptTokenizer::Token::BANG: operation->operation = UnaryOpNode::OP_LOGIC_NOT; operation->variant_op = Variant::OP_NOT; operation->operand = parse_precedence(PREC_LOGIC_NOT, false); + if (operation->operand == nullptr) { + push_error(vformat(R"(Expected expression after "%s" operator.)", op_type == GDScriptTokenizer::Token::NOT ? "not" : "!")); + } break; default: return nullptr; // Unreachable. @@ -2295,6 +2303,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_ternary_operator(Expressio operation->false_expr = parse_precedence(PREC_TERNARY, false); + if (operation->false_expr == nullptr) { + push_error(R"(Expected expression after "else".)"); + } + return operation; } @@ -2401,10 +2413,17 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode } assignment->assignee = p_previous_operand; assignment->assigned_value = parse_expression(false); + if (assignment->assigned_value == nullptr) { + push_error(R"(Expected an expression after "=".)"); + } #ifdef DEBUG_ENABLED - if (has_operator && source_variable != nullptr && source_variable->assignments == 0) { - push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name); + if (source_variable != nullptr) { + if (has_operator && source_variable->assignments == 0) { + push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name); + } + + source_variable->assignments += 1; } #endif @@ -2413,9 +2432,15 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode GDScriptParser::ExpressionNode *GDScriptParser::parse_await(ExpressionNode *p_previous_operand, bool p_can_assign) { AwaitNode *await = alloc_node<AwaitNode>(); - await->to_await = parse_precedence(PREC_AWAIT, false); + ExpressionNode *element = parse_precedence(PREC_AWAIT, false); + if (element == nullptr) { + push_error(R"(Expected signal or coroutine after "await".)"); + } + await->to_await = element; - current_function->is_coroutine = true; + if (current_function) { // Might be null in a getter or setter. + current_function->is_coroutine = true; + } return await; } @@ -2479,8 +2504,13 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode switch (dictionary->style) { case DictionaryNode::LUA_TABLE: - if (key != nullptr && key->type != Node::IDENTIFIER) { - push_error("Expected identifier as LUA-style dictionary key."); + if (key != nullptr && key->type != Node::IDENTIFIER && key->type != Node::LITERAL) { + push_error("Expected identifier or string as LUA-style dictionary key."); + advance(); + break; + } + if (key != nullptr && key->type == Node::LITERAL && static_cast<LiteralNode *>(key)->value.get_type() != Variant::STRING) { + push_error("Expected identifier or string as LUA-style dictionary key."); advance(); break; } @@ -2492,8 +2522,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode push_error(R"(Expected "=" after dictionary key.)"); } } - key->is_constant = true; - key->reduced_value = static_cast<IdentifierNode *>(key)->name; + if (key != nullptr) { + key->is_constant = true; + if (key->type == Node::IDENTIFIER) { + key->reduced_value = static_cast<IdentifierNode *>(key)->name; + } else if (key->type == Node::LITERAL) { + key->reduced_value = StringName(static_cast<LiteralNode *>(key)->value.operator String()); + } + } break; case DictionaryNode::PYTHON_DICT: if (!match(GDScriptTokenizer::Token::COLON)) { @@ -2572,6 +2608,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode * subscript->base = p_previous_operand; subscript->index = parse_expression(false); + if (subscript->index == nullptr) { + push_error(R"(Expected expression after "[".)"); + } + pop_multiline(); consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected "]" after subscription index.)"); @@ -2786,6 +2826,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p return lambda; } +GDScriptParser::ExpressionNode *GDScriptParser::parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign) { + push_error(R"("yield" was removed in Godot 4.0. Use "await" instead.)"); + return nullptr; +} + GDScriptParser::ExpressionNode *GDScriptParser::parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign) { // Just for better error messages. GDScriptTokenizer::Token::Type invalid = previous.type; @@ -2986,7 +3031,7 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & } else { /* Syntax: - @tutorial ( The Title Here ) : http://the.url/ + @tutorial ( The Title Here ) : https://the.url/ ^ open ^ close ^ colon ^ url */ int open_bracket_pos = begin_scan, close_bracket_pos = 0; @@ -3142,7 +3187,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty { nullptr, nullptr, PREC_NONE }, // TRAIT, { nullptr, nullptr, PREC_NONE }, // VAR, { nullptr, nullptr, PREC_NONE }, // VOID, - { nullptr, nullptr, PREC_NONE }, // YIELD, + { &GDScriptParser::parse_yield, nullptr, PREC_NONE }, // YIELD, // Punctuation { &GDScriptParser::parse_array, &GDScriptParser::parse_subscript, PREC_SUBSCRIPT }, // BRACKET_OPEN, { nullptr, nullptr, PREC_NONE }, // BRACKET_CLOSE, @@ -3363,10 +3408,10 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node variable->export_info.hint_string = Variant::get_type_name(export_type.builtin_type); break; case GDScriptParser::DataType::NATIVE: - if (ClassDB::is_parent_class(get_real_class_name(export_type.native_type), "Resource")) { + if (ClassDB::is_parent_class(export_type.native_type, "Resource")) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; - variable->export_info.hint_string = get_real_class_name(export_type.native_type); + variable->export_info.hint_string = export_type.native_type; } else { push_error(R"(Export type can only be built-in, a resource, or an enum.)", variable); return false; @@ -3422,55 +3467,47 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod ERR_FAIL_V_MSG(false, "Not implemented."); } -template <MultiplayerAPI::RPCMode t_mode> +template <Multiplayer::RPCMode t_mode> bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Node *p_node) { ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE && p_node->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to variables and functions.)", p_annotation->name)); - MultiplayerAPI::RPCConfig rpc_config; + Multiplayer::RPCConfig rpc_config; rpc_config.rpc_mode = t_mode; - for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) { - if (i == 0) { + if (p_annotation->resolved_arguments.size()) { + int last = p_annotation->resolved_arguments.size() - 1; + if (p_annotation->resolved_arguments[last].get_type() == Variant::INT) { + rpc_config.channel = p_annotation->resolved_arguments[last].operator int(); + last -= 1; + } + if (last > 3) { + push_error(R"(Invalid RPC arguments. At most 4 arguments are allowed, where only the last argument can be an integer to specify the channel.')", p_annotation); + return false; + } + for (int i = last; i >= 0; i--) { String mode = p_annotation->resolved_arguments[i].operator String(); - if (mode == "any") { - rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_REMOTE; - } else if (mode == "master") { - rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_MASTER; - } else if (mode == "puppet") { - rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_PUPPET; - } else { - push_error(R"(Invalid RPC mode. Must be one of: 'any', 'master', or 'puppet')", p_annotation); - return false; - } - } else if (i == 1) { - String sync = p_annotation->resolved_arguments[i].operator String(); - if (sync == "sync") { + if (mode == "any_peer") { + rpc_config.rpc_mode = Multiplayer::RPC_MODE_ANY_PEER; + } else if (mode == "authority") { + rpc_config.rpc_mode = Multiplayer::RPC_MODE_AUTHORITY; + } else if (mode == "call_local") { rpc_config.sync = true; - } else if (sync == "nosync") { + } else if (mode == "call_remote") { rpc_config.sync = false; - } else { - push_error(R"(Invalid RPC sync mode. Must be one of: 'sync' or 'nosync')", p_annotation); - return false; - } - } else if (i == 2) { - String mode = p_annotation->resolved_arguments[i].operator String(); - if (mode == "reliable") { - rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE; + } else if (mode == "reliable") { + rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE; } else if (mode == "unreliable") { - rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE; - } else if (mode == "ordered") { - rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE_ORDERED; + rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_UNRELIABLE; + } else if (mode == "unreliable_ordered") { + rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_UNRELIABLE_ORDERED; } else { - push_error(R"(Invalid RPC transfer mode. Must be one of: 'reliable', 'unreliable', 'ordered')", p_annotation); - return false; + push_error(R"(Invalid RPC argument. Must be one of: 'call_local'/'no_call_local' (local calls), 'any_peer'/'authority' (permission), 'reliable'/'unreliable'/'unreliable_ordered' (transfer mode).)", p_annotation); } - } else if (i == 3) { - rpc_config.channel = p_annotation->resolved_arguments[i].operator int(); } } switch (p_node->type) { case Node::FUNCTION: { FunctionNode *function = static_cast<FunctionNode *>(p_node); - if (function->rpc_config.rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) { + if (function->rpc_config.rpc_mode != Multiplayer::RPC_MODE_DISABLED) { push_error(R"(RPC annotations can only be used once per function.)", p_annotation); return false; } @@ -3575,6 +3612,39 @@ String GDScriptParser::DataType::to_string() const { ERR_FAIL_V_MSG("<unresolved type", "Kind set outside the enum range."); } +static Variant::Type _variant_type_to_typed_array_element_type(Variant::Type p_type) { + switch (p_type) { + case Variant::PACKED_BYTE_ARRAY: + case Variant::PACKED_INT32_ARRAY: + case Variant::PACKED_INT64_ARRAY: + return Variant::INT; + case Variant::PACKED_FLOAT32_ARRAY: + case Variant::PACKED_FLOAT64_ARRAY: + return Variant::FLOAT; + case Variant::PACKED_STRING_ARRAY: + return Variant::STRING; + case Variant::PACKED_VECTOR2_ARRAY: + return Variant::VECTOR2; + case Variant::PACKED_VECTOR3_ARRAY: + return Variant::VECTOR3; + case Variant::PACKED_COLOR_ARRAY: + return Variant::COLOR; + default: + return Variant::NIL; + } +} + +bool GDScriptParser::DataType::is_typed_container_type() const { + return kind == GDScriptParser::DataType::BUILTIN && _variant_type_to_typed_array_element_type(builtin_type) != Variant::NIL; +} + +GDScriptParser::DataType GDScriptParser::DataType::get_typed_container_type() const { + GDScriptParser::DataType type; + type.kind = GDScriptParser::DataType::BUILTIN; + type.builtin_type = _variant_type_to_typed_array_element_type(builtin_type); + return type; +} + /*---------- PRETTY PRINT FOR DEBUG ----------*/ #ifdef DEBUG_ENABLED diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 6a227a55e5..593fb0cc5e 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -31,8 +31,8 @@ #ifndef GDSCRIPT_PARSER_H #define GDSCRIPT_PARSER_H -#include "core/io/multiplayer_api.h" #include "core/io/resource.h" +#include "core/multiplayer/multiplayer.h" #include "core/object/ref_counted.h" #include "core/object/script_language.h" #include "core/string/string_name.h" @@ -161,6 +161,10 @@ public: container_element_type = nullptr; } + bool is_typed_container_type() const; + + GDScriptParser::DataType get_typed_container_type() const; + bool operator==(const DataType &p_other) const { if (type_source == UNDETECTED || p_other.type_source == UNDETECTED) { return true; // Can be consireded equal for parsing purposes. @@ -729,7 +733,7 @@ public: SuiteNode *body = nullptr; bool is_static = false; bool is_coroutine = false; - MultiplayerAPI::RPCConfig rpc_config; + Multiplayer::RPCConfig rpc_config; MethodInfo info; LambdaNode *source_lambda = nullptr; #ifdef TOOLS_ENABLED @@ -1320,7 +1324,7 @@ private: ClassNode *parse_class(); void parse_class_name(); void parse_extends(); - void parse_class_body(); + void parse_class_body(bool p_is_multiline); template <class T> void parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind); SignalNode *parse_signal(); @@ -1340,7 +1344,7 @@ private: template <PropertyHint t_hint, Variant::Type t_type> bool export_annotations(const AnnotationNode *p_annotation, Node *p_target); bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target); - template <MultiplayerAPI::RPCMode t_mode> + template <Multiplayer::RPCMode t_mode> bool network_annotations(const AnnotationNode *p_annotation, Node *p_target); // Statements. Node *parse_statement(); @@ -1384,6 +1388,7 @@ private: ExpressionNode *parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign); TypeNode *parse_type(bool p_allow_void = false); #ifdef TOOLS_ENABLED @@ -1399,7 +1404,6 @@ public: ClassNode *get_tree() const { return head; } bool is_tool() const { return _is_tool; } static Variant::Type get_builtin_type(const StringName &p_type); - static StringName get_real_class_name(const StringName &p_source); CompletionContext get_completion_context() const { return completion_context; } CompletionCall get_completion_call() const { return completion_call; } diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 3f14156dfa..d4a098811a 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -1440,7 +1440,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { GDScriptTokenizer::GDScriptTokenizer() { #ifdef TOOLS_ENABLED if (EditorSettings::get_singleton()) { - tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size"); + tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size"); } #endif // TOOLS_ENABLED } diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index 62531473c3..f1b0079536 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -317,9 +317,9 @@ struct GDScriptUtilityFunctionsDefinitions { d["@subpath"] = cp; d["@path"] = p->get_path(); - for (Map<StringName, GDScript::MemberInfo>::Element *E = base->member_indices.front(); E; E = E->next()) { - if (!d.has(E->key())) { - d[E->key()] = ins->members[E->get().index]; + for (const KeyValue<StringName, GDScript::MemberInfo> &E : base->member_indices) { + if (!d.has(E.key)) { + d[E.key] = ins->members[E.value.index]; } } *r_ret = d; @@ -396,9 +396,9 @@ struct GDScriptUtilityFunctionsDefinitions { GDScriptInstance *ins = static_cast<GDScriptInstance *>(static_cast<Object *>(*r_ret)->get_script_instance()); Ref<GDScript> gd_ref = ins->get_script(); - for (Map<StringName, GDScript::MemberInfo>::Element *E = gd_ref->member_indices.front(); E; E = E->next()) { - if (d.has(E->key())) { - ins->members.write[E->get().index] = d[E->key()]; + for (KeyValue<StringName, GDScript::MemberInfo> &E : gd_ref->member_indices) { + if (d.has(E.key)) { + ins->members.write[E.value.index] = d[E.key]; } } } diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 8a261a88e3..c89cf2f25c 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -88,9 +88,9 @@ static String _get_var_type(const Variant *p_var) { Object *bobj = p_var->get_validated_object_with_check(was_freed); if (!bobj) { if (was_freed) { - basestr = "null instance"; - } else { basestr = "previously freed"; + } else { + basestr = "null instance"; } } else { basestr = bobj->get_class(); @@ -174,7 +174,7 @@ void (*type_init_function_table[])(Variant *) = { &VariantInitializer<StringName>::init, // STRING_NAME. &VariantInitializer<NodePath>::init, // NODE_PATH. &VariantInitializer<RID>::init, // RID. - &VariantTypeAdjust<Object *>::adjust, // OBJECT. + &VariantInitializer<Object *>::init, // OBJECT. &VariantInitializer<Callable>::init, // CALLABLE. &VariantInitializer<Signal>::init, // SIGNAL. &VariantInitializer<Dictionary>::init, // DICTIONARY. @@ -322,6 +322,7 @@ void (*type_init_function_table[])(Variant *) = { &&OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, \ &&OPCODE_ITERATE_PACKED_COLOR_ARRAY, \ &&OPCODE_ITERATE_OBJECT, \ + &&OPCODE_STORE_GLOBAL, \ &&OPCODE_STORE_NAMED_GLOBAL, \ &&OPCODE_TYPE_ADJUST_BOOL, \ &&OPCODE_TYPE_ADJUST_INT, \ @@ -530,8 +531,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a memnew_placement(&stack[ADDR_STACK_CLASS], Variant(script)); - for (const Map<int, Variant::Type>::Element *E = temporary_slots.front(); E; E = E->next()) { - type_init_function_table[E->get()](&stack[E->key()]); + for (const KeyValue<int, Variant::Type> &E : temporary_slots) { + type_init_function_table[E.value](&stack[E.key]); } String err_text; @@ -1231,6 +1232,13 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(to_type < 0 || to_type >= Variant::VARIANT_MAX); +#ifdef DEBUG_ENABLED + if (src->operator Object *() && !src->get_validated_object()) { + err_text = "Trying to cast a freed object."; + OPCODE_BREAK; + } +#endif + Callable::CallError err; Variant::construct(to_type, *dst, (const Variant **)&src, 1, err); @@ -1255,6 +1263,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(!nc); #ifdef DEBUG_ENABLED + if (src->operator Object *() && !src->get_validated_object()) { + err_text = "Trying to cast a freed object."; + OPCODE_BREAK; + } if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { err_text = "Invalid cast: can't convert a non-object value to an object type."; OPCODE_BREAK; @@ -1283,6 +1295,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(!base_type); #ifdef DEBUG_ENABLED + if (src->operator Object *() && !src->get_validated_object()) { + err_text = "Trying to cast a freed object."; + OPCODE_BREAK; + } if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'."; OPCODE_BREAK; @@ -2122,7 +2138,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a retvalue = gdfs; - Error err = sig.connect(Callable(gdfs.ptr(), "_signal_callback"), varray(gdfs), Object::CONNECT_ONESHOT); + Error err = sig.connect(callable_bind(Callable(gdfs.ptr(), "_signal_callback"), retvalue), Object::CONNECT_ONESHOT); if (err != OK) { err_text = "Error connecting to signal: " + sig.get_name() + " during await."; OPCODE_BREAK; @@ -3116,6 +3132,18 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_STORE_GLOBAL) { + CHECK_SPACE(3); + int global_idx = _code_ptr[ip + 2]; + GD_ERR_BREAK(global_idx < 0 || global_idx >= GDScriptLanguage::get_singleton()->get_global_array_size()); + + GET_INSTRUCTION_ARG(dst, 0); + *dst = GDScriptLanguage::get_singleton()->get_global_array()[global_idx]; + + ip += 3; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_STORE_NAMED_GLOBAL) { CHECK_SPACE(3); int globalname_idx = _code_ptr[ip + 2]; diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index ad41b60a4e..7a483a16ba 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -145,6 +145,9 @@ String GDScriptWarning::get_message() const { case REDUNDANT_AWAIT: { return R"("await" keyword not needed in this case, because the expression isn't a coroutine nor a signal.)"; } + case EMPTY_FILE: { + return "Empty script file."; + } case WARNING_MAX: break; // Can't happen, but silences warning } @@ -190,6 +193,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "ASSERT_ALWAYS_TRUE", "ASSERT_ALWAYS_FALSE", "REDUNDANT_AWAIT", + "EMPTY_FILE", }; static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names."); diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index 4b295b5eb8..8de46b08c1 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -68,6 +68,7 @@ public: ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true. ASSERT_ALWAYS_FALSE, // Expression for assert argument is always false. REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine). + EMPTY_FILE, // A script file is empty. WARNING_MAX, }; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index d106b3b541..730e554476 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -491,7 +491,7 @@ String ExtendGDScriptParser::get_text_for_completion(const lsp::Position &p_curs return longthing; } -String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol, bool p_func_requred) const { +String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol, bool p_func_required) const { String longthing; int len = lines.size(); for (int i = 0; i < len; i++) { @@ -513,7 +513,7 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c longthing += first_part; longthing += String::chr(0xFFFF); //not unicode, represents the cursor - if (p_func_requred) { + if (p_func_required) { longthing += "("; // tell the parser this is a function call } longthing += last_part; @@ -532,6 +532,9 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const { ERR_FAIL_INDEX_V(p_position.line, lines.size(), ""); String line = lines[p_position.line]; + if (line.is_empty()) { + return ""; + } ERR_FAIL_INDEX_V(p_position.character, line.size(), ""); int start_pos = p_position.character; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index 28b9b3c82a..5d7b16765b 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -85,7 +85,7 @@ public: Error get_left_function_call(const lsp::Position &p_position, lsp::Position &r_func_pos, int &r_arg_index) const; String get_text_for_completion(const lsp::Position &p_cursor) const; - String get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol = "", bool p_func_requred = false) const; + String get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol = "", bool p_func_required = false) const; String get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const; String get_uri() const; diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index b6c48468f5..5cf1e0fc5f 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -212,11 +212,11 @@ void GDScriptLanguageProtocol::initialized(const Variant &p_params) { lsp::GodotCapabilities capabilities; DocTools *doc = EditorHelp::get_doc_data(); - for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) { + for (const KeyValue<String, DocData::ClassDoc> &E : doc->class_list) { lsp::GodotNativeClassInfo gdclass; - gdclass.name = E->get().name; - gdclass.class_doc = &(E->get()); - if (ClassDB::ClassInfo *ptr = ClassDB::classes.getptr(StringName(E->get().name))) { + gdclass.name = E.value.name; + gdclass.class_doc = &(E.value); + if (ClassDB::ClassInfo *ptr = ClassDB::classes.getptr(StringName(E.value.name))) { gdclass.class_info = ptr; } capabilities.native_classes.push_back(gdclass); @@ -284,6 +284,23 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia peer->res_queue.push_back(msg.utf8()); } +void GDScriptLanguageProtocol::request_client(const String &p_method, const Variant &p_params, int p_client_id) { + if (p_client_id == -1) { + ERR_FAIL_COND_MSG(latest_client_id == -1, + "GDScript LSP: Can't notify client as none was connected."); + p_client_id = latest_client_id; + } + ERR_FAIL_COND(!clients.has(p_client_id)); + Ref<LSPeer> peer = clients.get(p_client_id); + ERR_FAIL_COND(peer == nullptr); + + Dictionary message = make_request(p_method, p_params, next_server_id); + next_server_id++; + String msg = Variant(message).to_json_string(); + msg = format_output(msg); + peer->res_queue.push_back(msg.utf8()); +} + bool GDScriptLanguageProtocol::is_smart_resolve_enabled() const { return bool(_EDITOR_GET("network/language_server/enable_smart_resolve")); } diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h index 5a2dd55c46..899446fb42 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.h +++ b/modules/gdscript/language_server/gdscript_language_protocol.h @@ -79,6 +79,8 @@ private: int latest_client_id = 0; int next_client_id = 0; + int next_server_id = 0; + Ref<GDScriptTextDocument> text_document; Ref<GDScriptWorkspace> workspace; @@ -107,6 +109,7 @@ public: void stop(); void notify_client(const String &p_method, const Variant &p_params = Variant(), int p_client_id = -1); + void request_client(const String &p_method, const Variant &p_params = Variant(), int p_client_id = -1); bool is_smart_resolve_enabled() const; bool is_goto_native_symbols_enabled() const; diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp index c47164d95b..41a2f9e4ad 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -36,6 +36,7 @@ #include "editor/editor_node.h" GDScriptLanguageServer::GDScriptLanguageServer() { + _EDITOR_DEF("network/language_server/remote_host", host); _EDITOR_DEF("network/language_server/remote_port", port); _EDITOR_DEF("network/language_server/enable_smart_resolve", true); _EDITOR_DEF("network/language_server/show_native_symbols_in_editor", false); @@ -56,9 +57,10 @@ void GDScriptLanguageServer::_notification(int p_what) { } } break; case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + String host = String(_EDITOR_GET("network/language_server/remote_host")); int port = (int)_EDITOR_GET("network/language_server/remote_port"); bool use_thread = (bool)_EDITOR_GET("network/language_server/use_thread"); - if (port != this->port || use_thread != this->use_thread) { + if (host != this->host || port != this->port || use_thread != this->use_thread) { this->stop(); this->start(); } @@ -76,9 +78,10 @@ void GDScriptLanguageServer::thread_main(void *p_userdata) { } void GDScriptLanguageServer::start() { + host = String(_EDITOR_GET("network/language_server/remote_host")); port = (int)_EDITOR_GET("network/language_server/remote_port"); use_thread = (bool)_EDITOR_GET("network/language_server/use_thread"); - if (protocol.start(port, IPAddress("127.0.0.1")) == OK) { + if (protocol.start(port, IPAddress(host)) == OK) { EditorNode::get_log()->add_message("--- GDScript language server started ---", EditorLog::MSG_TYPE_EDITOR); if (use_thread) { thread_running = true; diff --git a/modules/gdscript/language_server/gdscript_language_server.h b/modules/gdscript/language_server/gdscript_language_server.h index 29c5ddd70e..85a44a8cc1 100644 --- a/modules/gdscript/language_server/gdscript_language_server.h +++ b/modules/gdscript/language_server/gdscript_language_server.h @@ -44,6 +44,7 @@ class GDScriptLanguageServer : public EditorPlugin { bool thread_running = false; bool started = false; bool use_thread = false; + String host = "127.0.0.1"; int port = 6008; static void thread_main(void *p_userdata); diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index 9574c765bc..92ce71f395 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -217,8 +217,8 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { arr = native_member_completions.duplicate(); - for (Map<String, ExtendGDScriptParser *>::Element *E = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.front(); E; E = E->next()) { - ExtendGDScriptParser *script = E->get(); + for (KeyValue<String, ExtendGDScriptParser *> &E : GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts) { + ExtendGDScriptParser *script = E.value; const Array &items = script->get_member_completions(); const int start_size = arr.size(); @@ -291,8 +291,8 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { } } else if (item.kind == lsp::CompletionItemKind::Event) { if (params.context.triggerKind == lsp::CompletionTriggerKind::TriggerCharacter && (params.context.triggerCharacter == "(")) { - const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; - item.insertText = quote_style + item.label + quote_style; + const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; + item.insertText = item.label.quote(quote_style); } } @@ -428,6 +428,9 @@ GDScriptTextDocument::~GDScriptTextDocument() { void GDScriptTextDocument::sync_script_content(const String &p_path, const String &p_content) { String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_path); + if (!path.begins_with("res://")) { + return; + } GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content); EditorFileSystem::get_singleton()->update_file(path); diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 1512b4bb89..371e3de419 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -42,6 +42,7 @@ #include "scene/resources/packed_scene.h" void GDScriptWorkspace::_bind_methods() { + ClassDB::bind_method(D_METHOD("apply_new_signal"), &GDScriptWorkspace::apply_new_signal); ClassDB::bind_method(D_METHOD("didDeleteFiles"), &GDScriptWorkspace::did_delete_files); ClassDB::bind_method(D_METHOD("symbol"), &GDScriptWorkspace::symbol); ClassDB::bind_method(D_METHOD("parse_script", "path", "content"), &GDScriptWorkspace::parse_script); @@ -52,6 +53,54 @@ void GDScriptWorkspace::_bind_methods() { ClassDB::bind_method(D_METHOD("generate_script_api", "path"), &GDScriptWorkspace::generate_script_api); } +void GDScriptWorkspace::apply_new_signal(Object *obj, String function, PackedStringArray args) { + String function_signature = "func " + function; + Ref<Script> script = obj->get_script(); + + String source = script->get_source_code(); + + if (source.find(function_signature) != -1) { + return; + } + + int first_class = source.find("\nclass "); + int start_line = 0; + if (first_class != -1) { + start_line = source.substr(0, first_class).split("\n").size(); + } else { + start_line = source.split("\n").size(); + } + + String function_body = "\n\n" + function_signature + "("; + for (int i = 0; i < args.size(); ++i) { + function_body += args[i]; + if (i < args.size() - 1) { + function_body += ", "; + } + } + function_body += ")"; + if (EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints")) { + function_body += " -> void"; + } + function_body += ":\n\tpass # Replace with function body.\n"; + + lsp::TextEdit text_edit; + + if (first_class != -1) { + function_body += "\n\n"; + } + text_edit.range.end.line = text_edit.range.start.line = start_line; + + text_edit.newText = function_body; + + String uri = get_file_uri(script->get_path()); + + lsp::ApplyWorkspaceEditParams params; + params.edit.add_edit(uri, text_edit); + + GDScriptLanguageProtocol::get_singleton()->request_client("workspace/applyEdit", params.to_json()); +} + void GDScriptWorkspace::did_delete_files(const Dictionary &p_params) { Array files = p_params["files"]; for (int i = 0; i < files.size(); ++i) { @@ -212,9 +261,9 @@ Array GDScriptWorkspace::symbol(const Dictionary &p_params) { String query = p_params["query"]; Array arr; if (!query.is_empty()) { - for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) { + for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) { Vector<lsp::DocumentedSymbolInformation> script_symbols; - E->get()->get_symbols().symbol_tree_as_list(E->key(), script_symbols); + E.value->get_symbols().symbol_tree_as_list(E.key, script_symbols); for (int i = 0; i < script_symbols.size(); ++i) { if (query.is_subsequence_ofi(script_symbols[i].name)) { lsp::DocumentedSymbolInformation symbol = script_symbols[i]; @@ -233,10 +282,10 @@ Error GDScriptWorkspace::initialize() { } DocTools *doc = EditorHelp::get_doc_data(); - for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) { - const DocData::ClassDoc &class_data = E->value(); + for (const KeyValue<String, DocData::ClassDoc> &E : doc->class_list) { + const DocData::ClassDoc &class_data = E.value; lsp::DocumentSymbol class_symbol; - String class_name = E->key(); + String class_name = E.key; class_symbol.name = class_name; class_symbol.native_class = class_name; class_symbol.kind = lsp::SymbolKind::Class; @@ -344,22 +393,25 @@ Error GDScriptWorkspace::initialize() { reload_all_workspace_scripts(); if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { - for (Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.front(); E; E = E->next()) { + for (const KeyValue<StringName, lsp::DocumentSymbol> &E : native_symbols) { ClassMembers members; - const lsp::DocumentSymbol &class_symbol = E->get(); + const lsp::DocumentSymbol &class_symbol = E.value; for (int i = 0; i < class_symbol.children.size(); i++) { const lsp::DocumentSymbol &symbol = class_symbol.children[i]; members.set(symbol.name, &symbol); } - native_members.set(E->key(), members); + native_members.set(E.key, members); } // cache member completions - for (Map<String, ExtendGDScriptParser *>::Element *S = scripts.front(); S; S = S->next()) { - S->get()->get_member_completions(); + for (const KeyValue<String, ExtendGDScriptParser *> &S : scripts) { + S.value->get_member_completions(); } } + EditorNode *editor_node = EditorNode::get_singleton(); + editor_node->connect("script_add_function_request", callable_mp(this, &GDScriptWorkspace::apply_new_signal)); + return OK; } @@ -551,7 +603,7 @@ void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<S } } -const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_requred) { +const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_required) { const lsp::DocumentSymbol *symbol = nullptr; String path = get_file_path(p_doc_pos.textDocument.uri); @@ -576,7 +628,10 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu } else { ScriptLanguage::LookupResult ret; - if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_requred), symbol_identifier, path, nullptr, ret)) { + if (symbol_identifier == "new" && parser->get_lines()[p_doc_pos.position.line].replace(" ", "").replace("\t", "").find("new(") > -1) { + symbol_identifier = "_init"; + } + if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_required), symbol_identifier, path, nullptr, ret)) { if (ret.type == ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION) { String target_script_path = path; if (!ret.script.is_null()) { @@ -630,8 +685,8 @@ void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionP class_ptr = native_members.next(class_ptr); } - for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) { - const ExtendGDScriptParser *script = E->get(); + for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) { + const ExtendGDScriptParser *script = E.value; const ClassMembers &members = script->get_members(); if (const lsp::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) { r_list.push_back(*symbol); @@ -731,12 +786,12 @@ GDScriptWorkspace::GDScriptWorkspace() { GDScriptWorkspace::~GDScriptWorkspace() { Set<String> cached_parsers; - for (Map<String, ExtendGDScriptParser *>::Element *E = parse_results.front(); E; E = E->next()) { - cached_parsers.insert(E->key()); + for (const KeyValue<String, ExtendGDScriptParser *> &E : parse_results) { + cached_parsers.insert(E.key); } - for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) { - cached_parsers.insert(E->key()); + for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) { + cached_parsers.insert(E.key); } for (Set<String>::Element *E = cached_parsers.front(); E; E = E->next()) { diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h index 9496677449..6f5600b5cf 100644 --- a/modules/gdscript/language_server/gdscript_workspace.h +++ b/modules/gdscript/language_server/gdscript_workspace.h @@ -62,6 +62,8 @@ protected: void list_script_files(const String &p_root_dir, List<String> &r_files); + void apply_new_signal(Object *obj, String function, PackedStringArray args); + public: String root; String root_uri; @@ -85,7 +87,7 @@ public: void publish_diagnostics(const String &p_path); void completion(const lsp::CompletionParams &p_params, List<ScriptCodeCompletionOption> *r_options); - const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_requred = false); + const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_required = false); void resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List<const lsp::DocumentSymbol *> &r_list); const lsp::DocumentSymbol *resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params); void resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list); diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index 9ac6c6bd4e..3710a84a28 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -263,19 +263,29 @@ struct WorkspaceEdit { */ Map<String, Vector<TextEdit>> changes; + _FORCE_INLINE_ void add_edit(const String &uri, const TextEdit &edit) { + if (changes.has(uri)) { + changes[uri].push_back(edit); + } else { + Vector<TextEdit> edits; + edits.push_back(edit); + changes[uri] = edits; + } + } + _FORCE_INLINE_ Dictionary to_json() const { Dictionary dict; Dictionary out_changes; - for (Map<String, Vector<TextEdit>>::Element *E = changes.front(); E; E = E->next()) { + for (const KeyValue<String, Vector<TextEdit>> &E : changes) { Array edits; - for (int i = 0; i < E->get().size(); ++i) { + for (int i = 0; i < E.value.size(); ++i) { Dictionary text_edit; - text_edit["range"] = E->get()[i].range.to_json(); - text_edit["newText"] = E->get()[i].newText; + text_edit["range"] = E.value[i].range.to_json(); + text_edit["newText"] = E.value[i].newText; edits.push_back(text_edit); } - out_changes[E->key()] = edits; + out_changes[E.key] = edits; } dict["changes"] = out_changes; @@ -1322,6 +1332,18 @@ struct DocumentSymbol { } }; +struct ApplyWorkspaceEditParams { + WorkspaceEdit edit; + + Dictionary to_json() { + Dictionary dict; + + dict["edit"] = edit.to_json(); + + return dict; + } +}; + struct NativeSymbolInspectParams { String native_class; String symbol_name; diff --git a/modules/gdscript/tests/README.md b/modules/gdscript/tests/README.md new file mode 100644 index 0000000000..6e54085962 --- /dev/null +++ b/modules/gdscript/tests/README.md @@ -0,0 +1,8 @@ +# GDScript integration tests + +The `scripts/` folder contains integration tests in the form of GDScript files +and output files. + +See the +[Integration tests for GDScript documentation](https://docs.godotengine.org/en/latest/development/cpp/unit_testing.html#integration-tests-for-gdscript) +for information about creating and running GDScript integration tests. diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 03a48bf071..c383830c82 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -48,11 +48,11 @@ namespace GDScriptTests { void init_autoloads() { - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); // First pass, add the constants so they exist before any script is loaded. - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->get(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.get(); if (info.is_singleton) { for (int i = 0; i < ScriptServer::get_language_count(); i++) { @@ -62,8 +62,8 @@ void init_autoloads() { } // Second pass, load into global constants. - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->get(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.get(); if (!info.is_singleton) { // Skip non-singletons since we don't have a scene tree here anyway. @@ -415,6 +415,7 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { TestResult result; result.status = GDTEST_OK; result.output = String(); + result.passed = false; Error err = OK; @@ -496,7 +497,12 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { } return result; } - + // Script files matching this pattern are allowed to not contain a test() function. + if (source_file.match("*.notest.gd")) { + enable_stdout(); + result.passed = check_output(result.output); + return result; + } // Test running. const Map<StringName, GDScriptFunction *>::Element *test_function_element = script->get_member_functions().find(GDScriptTestRunner::test_function_name); if (test_function_element == nullptr) { diff --git a/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.gd b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.gd new file mode 100644 index 0000000000..9b722ea50a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.gd @@ -0,0 +1,3 @@ +func test(): + # Error here. + print(2.2 << 4) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.out b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.out new file mode 100644 index 0000000000..7dee854d1a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid operands to operator <<, float and int. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.gd b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.gd new file mode 100644 index 0000000000..0a4f647f57 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.gd @@ -0,0 +1,3 @@ +func test(): + # Error here. + print(2 >> 4.4) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.out b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.out new file mode 100644 index 0000000000..1edbf47ec0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid operands to operator >>, int and float. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.gd new file mode 100644 index 0000000000..b84ccdce81 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.gd @@ -0,0 +1,5 @@ +class Vector2: + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.out new file mode 100644 index 0000000000..87863baf75 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The member "Vector2" cannot have the same name as a builtin type. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.gd new file mode 100644 index 0000000000..a7c0a29a69 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.gd @@ -0,0 +1,4 @@ +const Vector2 = 0 + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.out new file mode 100644 index 0000000000..87863baf75 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The member "Vector2" cannot have the same name as a builtin type. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.gd b/modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.gd new file mode 100644 index 0000000000..0ad2337c15 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.gd @@ -0,0 +1,5 @@ +const CONSTANT = 25 + + +func test(): + CONSTANT(123) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.out b/modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.out new file mode 100644 index 0000000000..f4051cd02c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Member "CONSTANT" is not a function. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.gd b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.gd new file mode 100644 index 0000000000..7a922cd73e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.gd @@ -0,0 +1,6 @@ +func test(): + var lua_dict = { + a = 1, + b = 2, + a = 3, # Duplicate isn't allowed. + } diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.out b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.out new file mode 100644 index 0000000000..ffdfa56645 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Key "a" was already used in this dictionary (at line 3). diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.gd b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.gd new file mode 100644 index 0000000000..933e737ac7 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.gd @@ -0,0 +1,6 @@ +func test(): + var lua_dict_with_string = { + a = 1, + b = 2, + "a" = 3, # Duplicate isn't allowed. + } diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.out b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.out new file mode 100644 index 0000000000..ffdfa56645 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Key "a" was already used in this dictionary (at line 3). diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.gd b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.gd new file mode 100644 index 0000000000..3b8c83e9cb --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.gd @@ -0,0 +1,6 @@ +func test(): + var python_dict = { + "a": 1, + "b": 2, + "a": 3, # Duplicate isn't allowed. + } diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.out b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.out new file mode 100644 index 0000000000..ffdfa56645 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Key "a" was already used in this dictionary (at line 3). diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.gd new file mode 100644 index 0000000000..cf9a0ce038 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.gd @@ -0,0 +1,7 @@ +enum Size { + # Error here. Enum values must be integers. + S = 0.0, +} + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.out new file mode 100644 index 0000000000..b315d20508 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Enum values must be integers. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.gd new file mode 100644 index 0000000000..930f91b389 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.gd @@ -0,0 +1,4 @@ +enum Vector2 { A, B } + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.out new file mode 100644 index 0000000000..87863baf75 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The member "Vector2" cannot have the same name as a builtin type. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.gd new file mode 100644 index 0000000000..cd9b8fabc4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.gd @@ -0,0 +1,7 @@ +enum Size { + # Error here. Enum values must be integers. + S = "hello", +} + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.out new file mode 100644 index 0000000000..b315d20508 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Enum values must be integers. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.gd new file mode 100644 index 0000000000..4346503fc2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.gd @@ -0,0 +1,6 @@ +func function(): + pass + + +func test(): + function = 25 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.out b/modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.out new file mode 100644 index 0000000000..5275183da2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a new value to a constant. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.gd b/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.gd new file mode 100644 index 0000000000..b8c0b7a8d3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.gd @@ -0,0 +1,3 @@ +func test(): + # Error here. Array indices must be integers. + print([0, 1][true]) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.out b/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.out new file mode 100644 index 0000000000..015ad756f8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid index type "bool" for a base of type "Array". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.gd b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.gd new file mode 100644 index 0000000000..c159e03140 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.gd @@ -0,0 +1,2 @@ +func test(): + print(true + true) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.out b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.out new file mode 100644 index 0000000000..c1dc7c7d08 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid operands to operator +, bool and bool. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.gd new file mode 100644 index 0000000000..6aec2e0796 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.gd @@ -0,0 +1,2 @@ +func test(): + print({"hello": "world"} + {"godot": "engine"}) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.out b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.out new file mode 100644 index 0000000000..1b4451edbe --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid operands "Dictionary" and "Dictionary" for "+" operator. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.gd b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.gd new file mode 100644 index 0000000000..eb2a6a0ce7 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.gd @@ -0,0 +1,2 @@ +func test(): + print("hello" + ["world"]) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.out b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.out new file mode 100644 index 0000000000..6d44c6c1bd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid operands "String" and "Array" for "+" operator. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.gd b/modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.gd new file mode 100644 index 0000000000..a7426e88da --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.gd @@ -0,0 +1,5 @@ +func test(): + var i = 12 + # Constants must be made of a constant, deterministic expression. + # A constant that depends on a variable's value is not a constant expression. + const TEST = 13 + i diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.out b/modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.out new file mode 100644 index 0000000000..c40830f123 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Assigned value for constant "TEST" isn't a constant expression. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.gd b/modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.gd new file mode 100644 index 0000000000..d88c02d6ee --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.gd @@ -0,0 +1,3 @@ +func test(): + # Number separators may not be placed at the beginning of a number. + var __ = _123 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.out b/modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.out new file mode 100644 index 0000000000..cfb558bf45 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Identifier "_123" not declared in the current scope. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/missing_argument.gd b/modules/gdscript/tests/scripts/analyzer/errors/missing_argument.gd new file mode 100644 index 0000000000..70bdadf291 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/missing_argument.gd @@ -0,0 +1,6 @@ +func args(a, b): + print(a) + print(b) + +func test(): + args(1,) diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_argument.out b/modules/gdscript/tests/scripts/analyzer/errors/missing_argument.out index fc2a891109..fc2a891109 100644 --- a/modules/gdscript/tests/scripts/parser/errors/missing_argument.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/missing_argument.out diff --git a/modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.gd b/modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.gd new file mode 100644 index 0000000000..059d774927 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.gd @@ -0,0 +1,4 @@ +var property = 25 + +func test(): + property() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.out b/modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.out new file mode 100644 index 0000000000..94d6c26a1a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Member "property" is not a function. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.gd b/modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.gd new file mode 100644 index 0000000000..91401d32fc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.gd @@ -0,0 +1,7 @@ +# See also `parser-warnings/shadowed-constant.gd`. +const TEST = 25 + + +func test(): + # Error here (trying to set a new value to a constant). + TEST = 50 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.out b/modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.out new file mode 100644 index 0000000000..5275183da2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a new value to a constant. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.gd b/modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.gd new file mode 100644 index 0000000000..97f3e55e81 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.gd @@ -0,0 +1,5 @@ +func test(): + const TEST = 25 + + # Error here (can't assign a new value to a constant). + TEST = 50 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.out b/modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.out new file mode 100644 index 0000000000..5275183da2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a new value to a constant. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.gd b/modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.gd new file mode 100644 index 0000000000..722a8fcdb7 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.gd @@ -0,0 +1,11 @@ +# `class` extends RefCounted by default. +class Say: + func say(): + super() + print("say something") + + +func test(): + # RefCounted doesn't have a `say()` method, so the `super()` call in the method + # definition will cause a run-time error. + Say.new().say() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.out b/modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.out new file mode 100644 index 0000000000..e3dbf81850 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Function "say()" not found in base RefCounted. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.gd new file mode 100644 index 0000000000..7cba29884c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.gd @@ -0,0 +1,4 @@ +var Vector2 + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.out new file mode 100644 index 0000000000..87863baf75 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The member "Vector2" cannot have the same name as a builtin type. diff --git a/modules/gdscript/tests/scripts/analyzer/features/as.gd b/modules/gdscript/tests/scripts/analyzer/features/as.gd new file mode 100644 index 0000000000..13a36147c0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/as.gd @@ -0,0 +1,16 @@ +func test(): + var some_bool = 5 as bool + var some_int = 5 as int + var some_float = 5 as float + print(typeof(some_bool)) + print(typeof(some_int)) + print(typeof(some_float)) + + print() + + var some_bool_typed := 5 as bool + var some_int_typed := 5 as int + var some_float_typed := 5 as float + print(typeof(some_bool_typed)) + print(typeof(some_int_typed)) + print(typeof(some_float_typed)) diff --git a/modules/gdscript/tests/scripts/analyzer/features/as.out b/modules/gdscript/tests/scripts/analyzer/features/as.out new file mode 100644 index 0000000000..bcf84aa6f6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/as.out @@ -0,0 +1,8 @@ +GDTEST_OK +1 +2 +3 + +1 +2 +3 diff --git a/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.gd b/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.gd new file mode 100644 index 0000000000..f64dce26c9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.gd @@ -0,0 +1,9 @@ +func inferred_parameter(param = null): + if param == null: + param = Node.new() + param.name = "Ok" + print(param.name) + param.free() + +func test(): + inferred_parameter() diff --git a/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.out b/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.out new file mode 100644 index 0000000000..0e9f482af4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.out @@ -0,0 +1,2 @@ +GDTEST_OK +Ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out index dc4348d9c3..2c8cc9c03f 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out +++ b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out @@ -1,3 +1,3 @@ GDTEST_OK Name is equal -True +true diff --git a/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.gd b/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.gd new file mode 100644 index 0000000000..ac66b78220 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.gd @@ -0,0 +1,3 @@ +func test(): + print(Color.html_is_valid("00ffff")) + print("OK") diff --git a/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out b/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out new file mode 100644 index 0000000000..ad6beeb646 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out @@ -0,0 +1,3 @@ +GDTEST_OK +true +OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.gd b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.gd new file mode 100644 index 0000000000..135b6c3d85 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.gd @@ -0,0 +1,16 @@ +extends Node + +const NO_TYPE_CONST = 0 +const TYPE_CONST: int = 1 +const GUESS_TYPE_CONST := 2 + +class Test: + var a = NO_TYPE_CONST + var b = TYPE_CONST + var c = GUESS_TYPE_CONST + +func test(): + var test_instance = Test.new() + prints("a", test_instance.a, test_instance.a == NO_TYPE_CONST) + prints("b", test_instance.b, test_instance.b == TYPE_CONST) + prints("c", test_instance.c, test_instance.c == GUESS_TYPE_CONST) diff --git a/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.out b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.out new file mode 100644 index 0000000000..a96bb84246 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.out @@ -0,0 +1,4 @@ +GDTEST_OK +a 0 true +b 1 true +c 2 true diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd new file mode 100644 index 0000000000..5f57c5b8c2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd @@ -0,0 +1,14 @@ +extends Node + +enum Named { VALUE_A, VALUE_B, VALUE_C = 42 } + +class Test: + var a = Named.VALUE_A + var b = Named.VALUE_B + var c = Named.VALUE_C + +func test(): + var test_instance = Test.new() + prints("a", test_instance.a, test_instance.a == Named.VALUE_A) + prints("b", test_instance.b, test_instance.b == Named.VALUE_B) + prints("c", test_instance.c, test_instance.c == Named.VALUE_C) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out new file mode 100644 index 0000000000..c160839da3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out @@ -0,0 +1,4 @@ +GDTEST_OK +a 0 true +b 1 true +c 42 true diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.gd new file mode 100644 index 0000000000..26edce353d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.gd @@ -0,0 +1,14 @@ +extends Node + +enum { VALUE_A, VALUE_B, VALUE_C = 42 } + +class Test: + var a = VALUE_A + var b = VALUE_B + var c = VALUE_C + +func test(): + var test_instance = Test.new() + prints("a", test_instance.a, test_instance.a == VALUE_A) + prints("b", test_instance.b, test_instance.b == VALUE_B) + prints("c", test_instance.c, test_instance.c == VALUE_C) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.out b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.out new file mode 100644 index 0000000000..c160839da3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.out @@ -0,0 +1,4 @@ +GDTEST_OK +a 0 true +b 1 true +c 42 true diff --git a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.gd b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.gd new file mode 100644 index 0000000000..fb0ace6a90 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.gd @@ -0,0 +1,5 @@ +func test(): + pass + +func something(): + return "OK" diff --git a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.out b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_class_as_return_type.gd b/modules/gdscript/tests/scripts/analyzer/features/inner_class_as_return_type.gd new file mode 100644 index 0000000000..4f4b7a4897 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/inner_class_as_return_type.gd @@ -0,0 +1,11 @@ +class InnerClass: + var val := "OK" + static func create_instance() -> InnerClass: + return new() + +func create_inner_instance() -> InnerClass: + return InnerClass.create_instance() + +func test(): + var instance = create_inner_instance() + print(instance.val) diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_class_as_return_type.out b/modules/gdscript/tests/scripts/analyzer/features/inner_class_as_return_type.out new file mode 100644 index 0000000000..1ccb591560 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/inner_class_as_return_type.out @@ -0,0 +1,2 @@ +GDTEST_OK +OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.gd b/modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.gd new file mode 100644 index 0000000000..569f95850f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.gd @@ -0,0 +1,2 @@ +func test(): + print(Color.html_is_valid("00ffff")) diff --git a/modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.out new file mode 100644 index 0000000000..55482c2b52 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.out @@ -0,0 +1,2 @@ +GDTEST_OK +true diff --git a/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd new file mode 100644 index 0000000000..5f73064cc0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd @@ -0,0 +1,5 @@ +const preloaded : GDScript = preload("gdscript_to_preload.gd") + +func test(): + var preloaded_instance: preloaded = preloaded.new() + print(preloaded_instance.something()) diff --git a/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.out b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.out new file mode 100644 index 0000000000..1ccb591560 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.out @@ -0,0 +1,2 @@ +GDTEST_OK +OK diff --git a/modules/gdscript/tests/scripts/parser/errors/array_consecutive_commas.gd b/modules/gdscript/tests/scripts/parser/errors/array_consecutive_commas.gd new file mode 100644 index 0000000000..b45f99fdd0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/array_consecutive_commas.gd @@ -0,0 +1,3 @@ +func test(): + # Arrays with consecutive commas are not allowed. + var array = ["arrays",,,,] diff --git a/modules/gdscript/tests/scripts/parser/errors/array_consecutive_commas.out b/modules/gdscript/tests/scripts/parser/errors/array_consecutive_commas.out new file mode 100644 index 0000000000..4ef8526065 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/array_consecutive_commas.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression as array element. diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.gd new file mode 100644 index 0000000000..17d5e078e5 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.gd @@ -0,0 +1,2 @@ +func test(): + var hello == "world" diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.out b/modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.out new file mode 100644 index 0000000000..b150fc0d16 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected end of statement after variable declaration, found "==" instead. diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.gd new file mode 100644 index 0000000000..8b5f620889 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.gd @@ -0,0 +1,2 @@ +func test(): + var hello === "world" diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.out b/modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.out new file mode 100644 index 0000000000..b150fc0d16 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected end of statement after variable declaration, found "==" instead. diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.gd new file mode 100644 index 0000000000..17c65ad60a --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.gd @@ -0,0 +1,3 @@ +func test(): + var a = 0 + a = diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.out b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.out new file mode 100644 index 0000000000..1369a7a0dc --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected an expression after "=". diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_if.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_in_if.gd new file mode 100644 index 0000000000..8c3a908532 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_if.gd @@ -0,0 +1,4 @@ +func test(): + # Error here. + if foo = 25: + print(foo) diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_if.out b/modules/gdscript/tests/scripts/parser/errors/assignment_in_if.out new file mode 100644 index 0000000000..e8f9130706 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_if.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Assignment is not allowed inside an expression. diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var.gd new file mode 100644 index 0000000000..126a3227ea --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var.gd @@ -0,0 +1,2 @@ +func test(): + var hello = "world" = "test" diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var.out b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var.out new file mode 100644 index 0000000000..e8f9130706 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Assignment is not allowed inside an expression. diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.gd new file mode 100644 index 0000000000..a99557fa3c --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.gd @@ -0,0 +1,4 @@ +func test(): + # Error here. + if var foo = 25: + print(foo) diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.out b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.out new file mode 100644 index 0000000000..e84f4652ac --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected conditional expression after "if". diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.gd new file mode 100644 index 0000000000..031ea523c8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.gd @@ -0,0 +1,2 @@ +func test(): + var = "world" diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.out b/modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.out new file mode 100644 index 0000000000..a4bd8beef1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected variable name after "var". diff --git a/modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.gd b/modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.gd new file mode 100644 index 0000000000..b52a6defcb --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.gd @@ -0,0 +1,2 @@ +func test(): + print(~) diff --git a/modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.out b/modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.out new file mode 100644 index 0000000000..ceabe42d3c --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression after "~" operator. diff --git a/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.gd b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.gd new file mode 100644 index 0000000000..b3ea1ba1f6 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.gd @@ -0,0 +1,2 @@ +func test(): + print(not) diff --git a/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.out b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.out new file mode 100644 index 0000000000..6cf191ea98 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression after "not" operator. diff --git a/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.gd b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.gd new file mode 100644 index 0000000000..8a33079193 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.gd @@ -0,0 +1,2 @@ +func test(): + print(!) diff --git a/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.out b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.out new file mode 100644 index 0000000000..87fcc5e2b0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression after "!" operator. diff --git a/modules/gdscript/tests/scripts/parser/errors/brace_syntax.gd b/modules/gdscript/tests/scripts/parser/errors/brace_syntax.gd new file mode 100644 index 0000000000..ab66537c93 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/brace_syntax.gd @@ -0,0 +1,3 @@ +func test() { + print("Hello world!"); +} diff --git a/modules/gdscript/tests/scripts/parser/errors/brace_syntax.out b/modules/gdscript/tests/scripts/parser/errors/brace_syntax.out new file mode 100644 index 0000000000..2f37a740ab --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/brace_syntax.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected ":" after function declaration. diff --git a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd new file mode 100644 index 0000000000..d13d713454 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd @@ -0,0 +1,6 @@ +# Error here. `class_name` should be used *before* annotations, not after. +@icon("res://path/to/optional/icon.svg") +class_name HelloWorld + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out new file mode 100644 index 0000000000..0bcc8acc55 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +"class_name" should be used before annotations. diff --git a/modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.gd b/modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.gd new file mode 100644 index 0000000000..49fb4ffedf --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.gd @@ -0,0 +1,3 @@ +func test(): + var TEST = 50 + const TEST = 25 diff --git a/modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.out b/modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.out new file mode 100644 index 0000000000..407f094ca0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +There is already a variable named "TEST" declared in this scope. diff --git a/modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.gd b/modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.gd new file mode 100644 index 0000000000..2581d873dd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.gd @@ -0,0 +1,7 @@ +func hello(arg1): + print(arg1) + + +func test(): + # Error here. + hello(arg1 = 25) diff --git a/modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.out b/modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.out new file mode 100644 index 0000000000..e8f9130706 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Assignment is not allowed inside an expression. diff --git a/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd new file mode 100644 index 0000000000..92dfb2366d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd @@ -0,0 +1,2 @@ +func test(): + var dictionary = { hello = "world",, } diff --git a/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.out b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.out new file mode 100644 index 0000000000..d1dcd1cb4b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression as dictionary key. diff --git a/modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.gd b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.gd new file mode 100644 index 0000000000..a8f7cf1810 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.gd @@ -0,0 +1,5 @@ +const test = 25 + + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.out b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.out new file mode 100644 index 0000000000..c614acd094 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Function "test" has the same name as a previously declared constant. diff --git a/modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.gd b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.gd new file mode 100644 index 0000000000..5c86710a40 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.gd @@ -0,0 +1,7 @@ +func test(): + pass + + +# Error here. The difference with `variable-conflicts-function.gd` is that here, +# the function is defined *before* the variable. +var test = 25 diff --git a/modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.out b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.out new file mode 100644 index 0000000000..551db61531 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Variable "test" has the same name as a previously declared function. diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.gd b/modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.gd new file mode 100644 index 0000000000..3b52f6e324 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.gd @@ -0,0 +1,2 @@ +func test(): + var escape = "invalid escape \h <- here" diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.out b/modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.out new file mode 100644 index 0000000000..32b4d004db --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Invalid escape in string. diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_number.gd b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_number.gd new file mode 100644 index 0000000000..081b9faf4b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_number.gd @@ -0,0 +1,3 @@ +func test(): + # Error here. + var 23test = "is not a valid identifier" diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_number.out b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_number.out new file mode 100644 index 0000000000..a4bd8beef1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_number.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected variable name after "var". diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.gd b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.gd new file mode 100644 index 0000000000..fa4d6b5cac --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.gd @@ -0,0 +1,3 @@ +func test(): + # Error here. + var "yes" = "is not a valid identifier" diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.out b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.out new file mode 100644 index 0000000000..a4bd8beef1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected variable name after "var". diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.gd b/modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.gd new file mode 100644 index 0000000000..c835ce15e1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.gd @@ -0,0 +1,5 @@ +func test(): + var amount = 50 + # C-style ternary operator is invalid in GDScript. + # The valid syntax is `"yes" if amount < 60 else "no"`, like in Python. + var ternary = amount < 60 ? "yes" : "no" diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.out b/modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.out new file mode 100644 index 0000000000..ac82d691b7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Unexpected "?" in source. If you want a ternary operator, use "truthy_value if true_condition else falsy_value". diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_argument.gd b/modules/gdscript/tests/scripts/parser/errors/missing_argument.gd deleted file mode 100644 index c56ad94095..0000000000 --- a/modules/gdscript/tests/scripts/parser/errors/missing_argument.gd +++ /dev/null @@ -1,6 +0,0 @@ -func args(a, b): - print(a) - print(b) - -func test(): - args(1,) diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd b/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd index a1077e1985..8af5f123cc 100644 --- a/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd +++ b/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd @@ -1,2 +1,2 @@ func test(): - var a = ("missing paren ->" + var a = ("missing paren ->" diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd b/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd index 62cb633e9e..0e5e5ce060 100644 --- a/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd +++ b/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd @@ -1,3 +1,3 @@ func test(): - if true # Missing colon here. - print("true") + if true # Missing colon here. + print("true") diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.gd b/modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.gd new file mode 100644 index 0000000000..1f66935329 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.gd @@ -0,0 +1,4 @@ +func test(): + var x = 1 if false else + print("oops") + print(x) diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.out b/modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.out new file mode 100644 index 0000000000..dab6b0a1ad --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression after "else". diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd b/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd index 116b0151da..7a35bf688c 100644 --- a/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd +++ b/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd @@ -1,6 +1,6 @@ func args(a, b): - print(a) - print(b) + print(a) + print(b) func test(): - args(1,2 + args(1,2 diff --git a/modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.gd b/modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.gd new file mode 100644 index 0000000000..193f824702 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.gd @@ -0,0 +1,3 @@ +func test(): + var a = 0 + print(a--) diff --git a/modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.out b/modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.out new file mode 100644 index 0000000000..b6b577a277 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression after "-" operator. diff --git a/modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.gd b/modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.gd new file mode 100644 index 0000000000..035d27638c --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.gd @@ -0,0 +1,3 @@ +func test(): + var a = 0 + print(a++) diff --git a/modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.out b/modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.out new file mode 100644 index 0000000000..24eb76593a --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression after "+" operator. diff --git a/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.gd b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.gd new file mode 100644 index 0000000000..71a03fbc0d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.gd @@ -0,0 +1,3 @@ +func test(): + # Number separators may not be placed right next to each other. + var __ = 1__23 diff --git a/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.out b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.out new file mode 100644 index 0000000000..71a3c2fd6a --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Only one underscore can be used as a numeric separator. diff --git a/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd b/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd index 3875ce3936..df388a21de 100644 --- a/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd +++ b/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd @@ -1,3 +1,5 @@ extends Node + + func test(): - var a = $ # Expected some node path. + var a = $ # Expected some node path. diff --git a/modules/gdscript/tests/scripts/parser/errors/redefine_keyword.gd b/modules/gdscript/tests/scripts/parser/errors/redefine_keyword.gd new file mode 100644 index 0000000000..c289c9d976 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/redefine_keyword.gd @@ -0,0 +1,2 @@ +func test(): + var while = "it's been a while" diff --git a/modules/gdscript/tests/scripts/parser/errors/redefine_keyword.out b/modules/gdscript/tests/scripts/parser/errors/redefine_keyword.out new file mode 100644 index 0000000000..a4bd8beef1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/redefine_keyword.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected variable name after "var". diff --git a/modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.gd b/modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.gd new file mode 100644 index 0000000000..204259f981 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.gd @@ -0,0 +1,5 @@ +func test(): + const TEST = 25 + + # Error here (can't redeclare a constant on the same scope). + const TEST = 50 diff --git a/modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.out b/modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.out new file mode 100644 index 0000000000..d67cc92953 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +There is already a constant named "TEST" declared in this scope. diff --git a/modules/gdscript/tests/scripts/parser/errors/subscript_without_index.gd b/modules/gdscript/tests/scripts/parser/errors/subscript_without_index.gd new file mode 100644 index 0000000000..c30c05e4da --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/subscript_without_index.gd @@ -0,0 +1,3 @@ +func test(): + var array = [1, 2, 3] + array[] = 4 diff --git a/modules/gdscript/tests/scripts/parser/errors/subscript_without_index.out b/modules/gdscript/tests/scripts/parser/errors/subscript_without_index.out new file mode 100644 index 0000000000..7017c7b4aa --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/subscript_without_index.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression after "[". diff --git a/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.gd b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.gd new file mode 100644 index 0000000000..0d8843df20 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.gd @@ -0,0 +1,3 @@ +func test(): + const TEST = 25 + var TEST = 50 diff --git a/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.out b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.out new file mode 100644 index 0000000000..d67cc92953 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +There is already a constant named "TEST" declared in this scope. diff --git a/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.gd b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.gd new file mode 100644 index 0000000000..ce2c8784d6 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.gd @@ -0,0 +1,6 @@ +var test = 25 + +# Error here. The difference with `variable-conflicts-function.gd` is that here, +# the function is defined *before* the variable. +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.out b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.out new file mode 100644 index 0000000000..daeaca40ec --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Function "test" has the same name as a previously declared variable. diff --git a/modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.gd b/modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.gd new file mode 100644 index 0000000000..8850892f2d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.gd @@ -0,0 +1,13 @@ +# The VCS conflict marker has only 6 `=` signs instead of 7 to prevent editors like +# Visual Studio Code from recognizing it as an actual VCS conflict marker. +# Nonetheless, the GDScript parser is still expected to find and report the VCS +# conflict marker error correctly. + +<<<<<<< HEAD +Hello world +====== +Goodbye +>>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086 + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.out b/modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.out new file mode 100644 index 0000000000..df9dab2223 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Unexpected "VCS conflict marker" in class body. diff --git a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd index 6fd2692d47..babe39068c 100644 --- a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd +++ b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd @@ -1,3 +1,5 @@ extends Node + + func test(): - $23 # Can't use number here. + $23 # Can't use number here. diff --git a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd index 1836d42226..b6b1cf3e52 100644 --- a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd +++ b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd @@ -1,3 +1,5 @@ extends Node + + func test(): - $MyNode/23 # Can't use number here. + $MyNode/23 # Can't use number here. diff --git a/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.gd b/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.gd new file mode 100644 index 0000000000..7862eff6ec --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.gd @@ -0,0 +1,6 @@ +#GDTEST_PARSER_ERROR + +signal event + +func test(): + yield("event") diff --git a/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.out b/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.out new file mode 100644 index 0000000000..36cb699e92 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +"yield" was removed in Godot 4.0. Use "await" instead. diff --git a/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd new file mode 100644 index 0000000000..43b513045b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd @@ -0,0 +1,34 @@ +func foo(x): + match x: + 1 + 1: + print("1+1") + [1,2,[1,{1:2,2:var z,..}]]: + print("[1,2,[1,{1:2,2:var z,..}]]") + print(z) + 1 if true else 2: + print("1 if true else 2") + 1 < 2: + print("1 < 2") + 1 or 2 and 1: + print("1 or 2 and 1") + 6 | 1: + print("1 | 1") + 1 >> 1: + print("1 >> 1") + 1, 2 or 3, 4: + print("1, 2 or 3, 4") + _: + print("wildcard") + +func test(): + foo(6 | 1) + foo(1 >> 1) + foo(2) + foo(1) + foo(1+1) + foo(1 < 2) + foo([2, 1]) + foo(4) + foo([1, 2, [1, {1 : 2, 2:3}]]) + foo([1, 2, [1, {1 : 2, 2:[1,3,5, "123"], 4:2}]]) + foo([1, 2, [1, {1 : 2}]]) diff --git a/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.out b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.out new file mode 100644 index 0000000000..67c7e28046 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.out @@ -0,0 +1,14 @@ +GDTEST_OK +1 | 1 +1 >> 1 +1+1 +1 if true else 2 +1+1 +1 < 2 +wildcard +1, 2 or 3, 4 +[1,2,[1,{1:2,2:var z,..}]] +3 +[1,2,[1,{1:2,2:var z,..}]] +[1, 3, 5, 123] +wildcard diff --git a/modules/gdscript/tests/scripts/parser/features/array.gd b/modules/gdscript/tests/scripts/parser/features/array.gd new file mode 100644 index 0000000000..828ce8d134 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/array.gd @@ -0,0 +1,16 @@ +func test(): + # Indexing from the beginning: + print([1, 2, 3][0]) + print([1, 2, 3][1]) + print([1, 2, 3][2]) + + # Indexing from the end: + print([1, 2, 3][-1]) + print([1, 2, 3][-2]) + print([1, 2, 3][-3]) + + # Float indices are currently allowed, but should probably be an error? + print([1, 2, 3][0.4]) + print([1, 2, 3][0.8]) + print([1, 2, 3][1.0]) + print([1, 2, 3][-1.0]) diff --git a/modules/gdscript/tests/scripts/parser/features/array.out b/modules/gdscript/tests/scripts/parser/features/array.out new file mode 100644 index 0000000000..cf576c59e0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/array.out @@ -0,0 +1,11 @@ +GDTEST_OK +1 +2 +3 +3 +2 +1 +1 +1 +2 +3 diff --git a/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd new file mode 100644 index 0000000000..2b46f1e88a --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd @@ -0,0 +1,27 @@ +func foo(x): + match x: + 1: + print("1") + 2: + print("2") + [1, 2]: + print("[1, 2]") + 3 or 4: + print("3 or 4") + 4: + print("4") + {1 : 2, 2 : 3}: + print("{1 : 2, 2 : 3}") + _: + print("wildcard") + +func test(): + foo(0) + foo(1) + foo(2) + foo([1, 2]) + foo(3) + foo(4) + foo([4,4]) + foo({1 : 2, 2 : 3}) + foo({1 : 2, 4 : 3}) diff --git a/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.out b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.out new file mode 100644 index 0000000000..46ee4b04da --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.out @@ -0,0 +1,10 @@ +GDTEST_OK +wildcard +1 +2 +[1, 2] +wildcard +4 +wildcard +{1 : 2, 2 : 3} +wildcard diff --git a/modules/gdscript/tests/scripts/parser/features/bitwise_operators.gd b/modules/gdscript/tests/scripts/parser/features/bitwise_operators.gd new file mode 100644 index 0000000000..de502c6ed1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/bitwise_operators.gd @@ -0,0 +1,50 @@ +enum Flags { + FIRE = 1 << 1, + ICE = 1 << 2, + SLIPPERY = 1 << 3, + STICKY = 1 << 4, + NONSOLID = 1 << 5, + + ALL = FIRE | ICE | SLIPPERY | STICKY | NONSOLID, +} + + +func test(): + var flags = Flags.FIRE | Flags.SLIPPERY + print(flags) + + flags = Flags.FIRE & Flags.SLIPPERY + print(flags) + + flags = Flags.FIRE ^ Flags.SLIPPERY + print(flags) + + flags = Flags.ALL & (Flags.FIRE | Flags.ICE) + print(flags) + + flags = (Flags.ALL & Flags.FIRE) | Flags.ICE + print(flags) + + flags = Flags.ALL & Flags.FIRE | Flags.ICE + print(flags) + + # Enum value must be casted to an integer. Otherwise, a parser error is emitted. + flags &= int(Flags.ICE) + print(flags) + + flags ^= int(Flags.ICE) + print(flags) + + flags |= int(Flags.STICKY | Flags.SLIPPERY) + print(flags) + + print() + + var num = 2 << 4 + print(num) + + num <<= 2 + print(num) + + num >>= 2 + print(num) diff --git a/modules/gdscript/tests/scripts/parser/features/bitwise_operators.out b/modules/gdscript/tests/scripts/parser/features/bitwise_operators.out new file mode 100644 index 0000000000..410e358a05 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/bitwise_operators.out @@ -0,0 +1,14 @@ +GDTEST_OK +10 +0 +10 +6 +6 +6 +4 +0 +24 + +32 +128 +32 diff --git a/modules/gdscript/tests/scripts/parser/features/class.gd b/modules/gdscript/tests/scripts/parser/features/class.gd new file mode 100644 index 0000000000..6652f85ad9 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/class.gd @@ -0,0 +1,25 @@ +# Test non-nested/slightly nested class architecture. +class Test: + var number = 25 + var string = "hello" + + +class TestSub extends Test: + var other_string = "bye" + + +class TestConstructor: + func _init(argument = 10): + print(str("constructor with argument ", argument)) + + +func test(): + var test_instance = Test.new() + test_instance.number = 42 + + var test_sub = TestSub.new() + assert(test_sub.number == 25) # From Test. + assert(test_sub.other_string == "bye") # From TestSub. + + TestConstructor.new() + TestConstructor.new(500) diff --git a/modules/gdscript/tests/scripts/parser/features/class.out b/modules/gdscript/tests/scripts/parser/features/class.out new file mode 100644 index 0000000000..94dc2d6003 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/class.out @@ -0,0 +1,3 @@ +GDTEST_OK +constructor with argument 10 +constructor with argument 500 diff --git a/modules/gdscript/tests/scripts/parser/features/class_inheritance.gd b/modules/gdscript/tests/scripts/parser/features/class_inheritance.gd new file mode 100644 index 0000000000..3f9b4ea86e --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/class_inheritance.gd @@ -0,0 +1,33 @@ +# Test deeply nested class architectures. +class Test: + var depth = 1 + + class Nested: + var depth_nested = 10 + + +class Test2 extends Test: + var depth2 = 2 + + +class Test3 extends Test2: + var depth3 = 3 + + +class Test4 extends Test3: + var depth4 = 4 + + class Nested2: + var depth4_nested = 100 + + +func test(): + print(Test.new().depth) + print(Test2.new().depth) + print(Test2.new().depth2) + print(Test3.new().depth) + print(Test3.new().depth3) + print(Test4.new().depth) + print(Test4.new().depth4) + print(Test.Nested.new().depth_nested) + print(Test4.Nested2.new().depth4_nested) diff --git a/modules/gdscript/tests/scripts/parser/features/class_inheritance.out b/modules/gdscript/tests/scripts/parser/features/class_inheritance.out new file mode 100644 index 0000000000..75bdde3d94 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/class_inheritance.out @@ -0,0 +1,10 @@ +GDTEST_OK +1 +1 +2 +1 +3 +1 +4 +10 +100 diff --git a/modules/gdscript/tests/scripts/parser/features/class_name.gd b/modules/gdscript/tests/scripts/parser/features/class_name.gd new file mode 100644 index 0000000000..8bd188e247 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/class_name.gd @@ -0,0 +1,5 @@ +class_name HelloWorld +@icon("res://path/to/optional/icon.svg") + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/features/class_name.out b/modules/gdscript/tests/scripts/parser/features/class_name.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/class_name.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/features/concatenation.gd b/modules/gdscript/tests/scripts/parser/features/concatenation.gd new file mode 100644 index 0000000000..e8335c9823 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/concatenation.gd @@ -0,0 +1,4 @@ +func test(): + print(20 + 20) + print("hello" + "world") + print([1, 2] + [3, 4]) diff --git a/modules/gdscript/tests/scripts/parser/features/concatenation.out b/modules/gdscript/tests/scripts/parser/features/concatenation.out new file mode 100644 index 0000000000..23bff08f49 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/concatenation.out @@ -0,0 +1,4 @@ +GDTEST_OK +40 +helloworld +[1, 2, 3, 4] diff --git a/modules/gdscript/tests/scripts/parser/features/constants.gd b/modules/gdscript/tests/scripts/parser/features/constants.gd new file mode 100644 index 0000000000..013c9c074f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/constants.gd @@ -0,0 +1,11 @@ +func test(): + const _TEST = 12 + 34 - 56 * 78 + const _STRING = "yes" + const _VECTOR = Vector2(5, 6) + const _ARRAY = [] + const _DICTIONARY = {"this": "dictionary"} + + # Create user constants from built-in constants. + const _HELLO = PI + TAU + const _INFINITY = INF + const _NOT_A_NUMBER = NAN diff --git a/modules/gdscript/tests/scripts/parser/features/constants.out b/modules/gdscript/tests/scripts/parser/features/constants.out new file mode 100644 index 0000000000..6093e4a6ca --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/constants.out @@ -0,0 +1,33 @@ +GDTEST_OK +>> WARNING +>> Line: 2 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_TEST' is declared but never used in the block. If this is intended, prefix it with an underscore: '__TEST' +>> WARNING +>> Line: 3 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_STRING' is declared but never used in the block. If this is intended, prefix it with an underscore: '__STRING' +>> WARNING +>> Line: 4 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_VECTOR' is declared but never used in the block. If this is intended, prefix it with an underscore: '__VECTOR' +>> WARNING +>> Line: 5 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_ARRAY' is declared but never used in the block. If this is intended, prefix it with an underscore: '__ARRAY' +>> WARNING +>> Line: 6 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_DICTIONARY' is declared but never used in the block. If this is intended, prefix it with an underscore: '__DICTIONARY' +>> WARNING +>> Line: 9 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_HELLO' is declared but never used in the block. If this is intended, prefix it with an underscore: '__HELLO' +>> WARNING +>> Line: 10 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_INFINITY' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INFINITY' +>> WARNING +>> Line: 11 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_NOT_A_NUMBER' is declared but never used in the block. If this is intended, prefix it with an underscore: '__NOT_A_NUMBER' diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary.gd b/modules/gdscript/tests/scripts/parser/features/dictionary.gd new file mode 100644 index 0000000000..99afe166c7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/dictionary.gd @@ -0,0 +1,37 @@ +func test(): + # Non-string keys are valid. + print({ 12: "world" }[12]) + + var contents = { + 0: "zero", + 0.0: "zero point zero", + null: "null", + false: "false", + []: "empty array", + Vector2i(): "zero Vector2i", + 15: { + 22: { + 4: ["nesting", "arrays"], + }, + }, + } + + print(contents[0.0]) + # Making sure declaration order doesn't affect things... + print({ 0.0: "zero point zero", 0: "zero", null: "null", false: "false", []: "empty array" }[0]) + print({ 0.0: "zero point zero", 0: "zero", null: "null", false: "false", []: "empty array" }[0.0]) + + print(contents[null]) + print(contents[false]) + print(contents[[]]) + print(contents[Vector2i()]) + print(contents[15]) + print(contents[15][22]) + print(contents[15][22][4]) + print(contents[15][22][4][0]) + print(contents[15][22][4][1]) + + # Currently fails with "invalid get index 'hello' on base Dictionary". + # Both syntaxes are valid however. + #print({ "hello": "world" }["hello"]) + #print({ "hello": "world" }.hello) diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary.out b/modules/gdscript/tests/scripts/parser/features/dictionary.out new file mode 100644 index 0000000000..54083c1afc --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/dictionary.out @@ -0,0 +1,14 @@ +GDTEST_OK +world +zero point zero +zero +zero point zero +null +false +empty array +zero Vector2i +{22:{4:[nesting, arrays]}} +{4:[nesting, arrays]} +[nesting, arrays] +nesting +arrays diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.gd b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.gd new file mode 100644 index 0000000000..fdd6de2348 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.gd @@ -0,0 +1,9 @@ +func test(): + var lua_dict = { + a = 1, + "b" = 2, # Using strings are allowed too. + "with spaces" = 3, # Especially useful when key has spaces... + "2" = 4, # ... or invalid identifiers. + } + + print(lua_dict) diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out new file mode 100644 index 0000000000..447d7e223c --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out @@ -0,0 +1,2 @@ +GDTEST_OK +{2:4, a:1, b:2, with spaces:3} diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.gd b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.gd new file mode 100644 index 0000000000..cce8538ddd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.gd @@ -0,0 +1,12 @@ +func test(): + # Mixing Python-style and Lua-style syntax in the same dictionary declaration + # is allowed. + var dict = { + "hello": { + world = { + "is": "beautiful", + }, + }, + } + + print(dict) diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out new file mode 100644 index 0000000000..62be807a1f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out @@ -0,0 +1,2 @@ +GDTEST_OK +{hello:{world:{is:beautiful}}} diff --git a/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd new file mode 100644 index 0000000000..8ba558e91d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd @@ -0,0 +1,20 @@ +extends Node + + +func test(): + # Create the required node structure. + var hello = Node.new() + hello.name = "Hello" + add_child(hello) + var world = Node.new() + world.name = "World" + hello.add_child(world) + + # All the ways of writing node paths below with the `$` operator are valid. + # Results are assigned to variables to avoid warnings. + var __ = $Hello + __ = $"Hello" + __ = $Hello/World + __ = $"Hello/World" + __ = $"Hello/.." + __ = $"Hello/../Hello/World" diff --git a/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.out b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/features/enum.gd b/modules/gdscript/tests/scripts/parser/features/enum.gd new file mode 100644 index 0000000000..bbc66f6f3d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/enum.gd @@ -0,0 +1,14 @@ +enum Size { + S = -10, + M, + L = 0, + XL = 10, + XXL, +} + +func test(): + print(Size.S) + print(Size.M) + print(Size.L) + print(Size.XL) + print(Size.XXL) diff --git a/modules/gdscript/tests/scripts/parser/features/enum.out b/modules/gdscript/tests/scripts/parser/features/enum.out new file mode 100644 index 0000000000..6f3a4a3e49 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/enum.out @@ -0,0 +1,6 @@ +GDTEST_OK +-10 +-9 +0 +10 +11 diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd new file mode 100644 index 0000000000..1e072728fc --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd @@ -0,0 +1,18 @@ +@export var example = 99 +@export_range(0, 100) var example_range = 100 +@export_range(0, 100, 1) var example_range_step = 101 +@export_range(0, 100, 1, "or_greater") var example_range_step_or_greater = 102 + +@export var color: Color +@export_color_no_alpha var color_no_alpha: Color +@export_node_path(Sprite2D, Sprite3D, Control, Node) var nodepath := ^"hello" + + +func test(): + print(example) + print(example_range) + print(example_range_step) + print(example_range_step_or_greater) + print(color) + print(color_no_alpha) + print(nodepath) diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.out b/modules/gdscript/tests/scripts/parser/features/export_variable.out new file mode 100644 index 0000000000..bae35e75c6 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out @@ -0,0 +1,8 @@ +GDTEST_OK +99 +100 +101 +102 +(0, 0, 0, 1) +(0, 0, 0, 1) +hello diff --git a/modules/gdscript/tests/scripts/parser/features/float_notation.gd b/modules/gdscript/tests/scripts/parser/features/float_notation.gd new file mode 100644 index 0000000000..b207b88820 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/float_notation.gd @@ -0,0 +1,24 @@ +func test(): + # The following floating-point notations are all valid: + print(is_equal_approx(123., 123)) + print(is_equal_approx(.123, 0.123)) + print(is_equal_approx(.123e4, 1230)) + print(is_equal_approx(123.e4, 1.23e6)) + print(is_equal_approx(.123e-1, 0.0123)) + print(is_equal_approx(123.e-1, 12.3)) + + # Same as above, but with negative numbers. + print(is_equal_approx(-123., -123)) + print(is_equal_approx(-.123, -0.123)) + print(is_equal_approx(-.123e4, -1230)) + print(is_equal_approx(-123.e4, -1.23e6)) + print(is_equal_approx(-.123e-1, -0.0123)) + print(is_equal_approx(-123.e-1, -12.3)) + + # Same as above, but with explicit positive numbers (which is redundant). + print(is_equal_approx(+123., +123)) + print(is_equal_approx(+.123, +0.123)) + print(is_equal_approx(+.123e4, +1230)) + print(is_equal_approx(+123.e4, +1.23e6)) + print(is_equal_approx(+.123e-1, +0.0123)) + print(is_equal_approx(+123.e-1, +12.3)) diff --git a/modules/gdscript/tests/scripts/parser/features/float_notation.out b/modules/gdscript/tests/scripts/parser/features/float_notation.out new file mode 100644 index 0000000000..041c4439b0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/float_notation.out @@ -0,0 +1,19 @@ +GDTEST_OK +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true diff --git a/modules/gdscript/tests/scripts/parser/features/for_range.gd b/modules/gdscript/tests/scripts/parser/features/for_range.gd new file mode 100644 index 0000000000..fd1d002b82 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/for_range.gd @@ -0,0 +1,39 @@ +func test(): + for i in range(5): + print(i) + + print() + + # Equivalent to the above `for` loop: + for i in 5: + print(i) + + print() + + for i in range(1, 5): + print(i) + + print() + + for i in range(1, -5, -1): + print(i) + + print() + + for i in [2, 4, 6, -8]: + print(i) + + print() + + for i in [true, false]: + print(i) + + print() + + for i in [Vector2i(10, 20), Vector2i(-30, -40)]: + print(i) + + print() + + for i in "Hello_Unicôde_world!_🦄": + print(i) diff --git a/modules/gdscript/tests/scripts/parser/features/for_range.out b/modules/gdscript/tests/scripts/parser/features/for_range.out new file mode 100644 index 0000000000..50b2c856c5 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/for_range.out @@ -0,0 +1,58 @@ +GDTEST_OK +0 +1 +2 +3 +4 + +0 +1 +2 +3 +4 + +1 +2 +3 +4 + +1 +0 +-1 +-2 +-3 +-4 + +2 +4 +6 +-8 + +true +false + +(10, 20) +(-30, -40) + +H +e +l +l +o +_ +U +n +i +c +ô +d +e +_ +w +o +r +l +d +! +_ +🦄 diff --git a/modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.gd b/modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.gd new file mode 100644 index 0000000000..f5098b00ae --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.gd @@ -0,0 +1,5 @@ +func example(_number: int, _number2: int = 5, number3 := 10): + return number3 + +func test(): + print(example(3)) diff --git a/modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.out b/modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.out new file mode 100644 index 0000000000..404cd41fe5 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.out @@ -0,0 +1,2 @@ +GDTEST_OK +10 diff --git a/modules/gdscript/tests/scripts/parser/features/function_many_parameters.gd b/modules/gdscript/tests/scripts/parser/features/function_many_parameters.gd new file mode 100644 index 0000000000..01edb37cec --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/function_many_parameters.gd @@ -0,0 +1,5 @@ +func example(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25, arg26, arg27, arg28, arg29, arg30, arg31, arg32, arg33, arg34, arg35, arg36, arg37, arg38, arg39, arg40, arg41, arg42, arg43, arg44, arg45, arg46, arg47, arg48 = false, arg49 = true, arg50 = null): + print(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25, arg26, arg27, arg28, arg29, arg30, arg31, arg32, arg33, arg34, arg35, arg36, arg37, arg38, arg39, arg40, arg41, arg42, arg43, arg44, arg45, arg46, arg47, arg48, arg49, arg50) + +func test(): + example(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47) diff --git a/modules/gdscript/tests/scripts/parser/features/function_many_parameters.out b/modules/gdscript/tests/scripts/parser/features/function_many_parameters.out new file mode 100644 index 0000000000..3a979227d4 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/function_many_parameters.out @@ -0,0 +1,2 @@ +GDTEST_OK +123456789101112131415161718192212223242526272829303132333435363738394041424344454647falsetruenull diff --git a/modules/gdscript/tests/scripts/parser/features/in.gd b/modules/gdscript/tests/scripts/parser/features/in.gd new file mode 100644 index 0000000000..f7296017c5 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/in.gd @@ -0,0 +1,14 @@ +func test(): + print("dot" in "Godot") + print(not "i" in "team") + + print(true in [true, false]) + print(not null in [true, false]) + print(null in [null]) + + print(26 in [8, 26, 64, 100]) + print(not Vector2i(10, 20) in [Vector2i(20, 10)]) + + print("apple" in { "apple": "fruit" }) + print("apple" in { "apple": null }) + print(not "apple" in { "fruit": "apple" }) diff --git a/modules/gdscript/tests/scripts/parser/features/in.out b/modules/gdscript/tests/scripts/parser/features/in.out new file mode 100644 index 0000000000..7533f6ff54 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/in.out @@ -0,0 +1,11 @@ +GDTEST_OK +true +true +true +true +true +true +true +true +true +true diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_callable.gd b/modules/gdscript/tests/scripts/parser/features/lambda_callable.gd new file mode 100644 index 0000000000..c3b2506156 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/lambda_callable.gd @@ -0,0 +1,4 @@ +func test(): + var my_lambda = func(x): + print(x) + my_lambda.call("hello") diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_callable.out b/modules/gdscript/tests/scripts/parser/features/lambda_callable.out new file mode 100644 index 0000000000..58774d2d3f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/lambda_callable.out @@ -0,0 +1,2 @@ +GDTEST_OK +hello diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.gd b/modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.gd new file mode 100644 index 0000000000..f081a0b6a7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.gd @@ -0,0 +1,4 @@ +func test(): + var x = 42 + var my_lambda = func(): print(x) + my_lambda.call() # Prints "42". diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.out b/modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.out new file mode 100644 index 0000000000..0982f3718c --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.out @@ -0,0 +1,2 @@ +GDTEST_OK +42 diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_named_callable.gd b/modules/gdscript/tests/scripts/parser/features/lambda_named_callable.gd new file mode 100644 index 0000000000..7971ca72a6 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/lambda_named_callable.gd @@ -0,0 +1,10 @@ +func i_take_lambda(lambda: Callable, param: String): + lambda.call(param) + + +func test(): + var my_lambda := func this_is_lambda(x): + print("Hello") + print("This is %s" % x) + + i_take_lambda(my_lambda, "a lambda") diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_named_callable.out b/modules/gdscript/tests/scripts/parser/features/lambda_named_callable.out new file mode 100644 index 0000000000..c627187d82 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/lambda_named_callable.out @@ -0,0 +1,3 @@ +GDTEST_OK +Hello +This is a lambda diff --git a/modules/gdscript/tests/scripts/parser/features/match.gd b/modules/gdscript/tests/scripts/parser/features/match.gd new file mode 100644 index 0000000000..4d05490aa5 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/match.gd @@ -0,0 +1,18 @@ +func test(): + var i = "Hello" + match i: + "Hello": + print("hello") + # This will fall through to the default case below. + continue + "Good bye": + print("bye") + _: + print("default") + + var j = 25 + match j: + 26: + print("This won't match") + _: + print("This will match") diff --git a/modules/gdscript/tests/scripts/parser/features/match.out b/modules/gdscript/tests/scripts/parser/features/match.out new file mode 100644 index 0000000000..732885c7a2 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/match.out @@ -0,0 +1,4 @@ +GDTEST_OK +hello +default +This will match diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_arrays.gd b/modules/gdscript/tests/scripts/parser/features/multiline_arrays.gd new file mode 100644 index 0000000000..3b30998853 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_arrays.gd @@ -0,0 +1,7 @@ +func test(): + var __ = [ + "this", + "is", "a","multiline", + + "array", "with mixed indentation and trailing comma", + ] diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_arrays.out b/modules/gdscript/tests/scripts/parser/features/multiline_arrays.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_arrays.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.gd b/modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.gd new file mode 100644 index 0000000000..e108cd23d4 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.gd @@ -0,0 +1,10 @@ +func test(): + var __ = { + "multiline": "dictionary","should": "work", + "even with": "a trailing comma", + } + + __ = { + this_also_applies = "to the", + lua_style_syntax = null, foo = null, + } diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.out b/modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_if.gd b/modules/gdscript/tests/scripts/parser/features/multiline_if.gd new file mode 100644 index 0000000000..86152f4543 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_if.gd @@ -0,0 +1,14 @@ +func test(): + # Line breaks are allowed within parentheses. + if ( + 1 == 1 + and 2 == 2 and + 3 == 3 + ): + pass + + # Alternatively, backslashes can be used. + if 1 == 1 \ + and 2 == 2 and \ + 3 == 3: + pass diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_if.out b/modules/gdscript/tests/scripts/parser/features/multiline_if.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_if.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_strings.gd b/modules/gdscript/tests/scripts/parser/features/multiline_strings.gd new file mode 100644 index 0000000000..7f5bba85e7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_strings.gd @@ -0,0 +1,15 @@ +func test(): + var __ = """ + This is a standalone string, not a multiline comment. + Writing both "double" quotes and 'simple' quotes is fine as + long as there is only ""one"" or ''two'' of those in a row, not more. + + If you have more quotes, they need to be escaped like this: \"\"\" + """ + __ = ''' + Another standalone string, this time with single quotes. + Writing both "double" quotes and 'simple' quotes is fine as + long as there is only ""one"" or ''two'' of those in a row, not more. + + If you have more quotes, they need to be escaped like this: \'\'\' + ''' diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_strings.out b/modules/gdscript/tests/scripts/parser/features/multiline_strings.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_strings.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_vector.gd b/modules/gdscript/tests/scripts/parser/features/multiline_vector.gd new file mode 100644 index 0000000000..11a40fc00e --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_vector.gd @@ -0,0 +1,17 @@ +func test(): + Vector2( + 1, + 2 + ) + + Vector3( + 3, + 3.5, + 4, # Trailing comma should work. + ) + + Vector2i(1, 2,) # Trailing comma should work. + + Vector3i(6, + 9, + 12) diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_vector.out b/modules/gdscript/tests/scripts/parser/features/multiline_vector.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_vector.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/features/nested_arithmetic.gd b/modules/gdscript/tests/scripts/parser/features/nested_arithmetic.gd new file mode 100644 index 0000000000..b9bd19c9c5 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_arithmetic.gd @@ -0,0 +1,22 @@ +func test(): + print(+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++2.718) + + print() + + print(------------------------------------------------------------------2.718) + print(-------------------------------------------------------------------2.718) + + print() + + print(~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~999) + + print() + + print(+-+-+-----+------------+++++++---++--++--+--+---+--++2.718) + + print() + + print(2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 0.444) + print(153023902390239 % 550 % 29 % 27 % 23 % 17) + print(2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2) + print(8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ -8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8) diff --git a/modules/gdscript/tests/scripts/parser/features/nested_arithmetic.out b/modules/gdscript/tests/scripts/parser/features/nested_arithmetic.out new file mode 100644 index 0000000000..048cfbdfae --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_arithmetic.out @@ -0,0 +1,82 @@ +GDTEST_OK +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +2.718 + +2.718 +-2.718 + +-1000 + +-2.718 + +36900.9009009009 +2 +8192 +-8 diff --git a/modules/gdscript/tests/scripts/parser/features/nested_array.gd b/modules/gdscript/tests/scripts/parser/features/nested_array.gd new file mode 100644 index 0000000000..3caef96391 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_array.gd @@ -0,0 +1,5 @@ +func test(): + var array = [[[[[[[[[[15]]]]]]]]]] + print(array[0][0][0][0][0][0][0][0]) + print(array[0][0][0][0][0][0][0][0][0]) + print(array[0][0][0][0][0][0][0][0][0][0]) diff --git a/modules/gdscript/tests/scripts/parser/features/nested_array.out b/modules/gdscript/tests/scripts/parser/features/nested_array.out new file mode 100644 index 0000000000..46c6ce3874 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_array.out @@ -0,0 +1,4 @@ +GDTEST_OK +[[15]] +[15] +15 diff --git a/modules/gdscript/tests/scripts/parser/features/nested_dictionary.gd b/modules/gdscript/tests/scripts/parser/features/nested_dictionary.gd new file mode 100644 index 0000000000..d67e142156 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_dictionary.gd @@ -0,0 +1,6 @@ +func test(): + var dictionary = {1: {2: {3: {4: {5: {6: {7: {8: {"key": "value"}}}}}}}}} + print(dictionary[1][2][3][4][5][6][7]) + print(dictionary[1][2][3][4][5][6][7][8]) + print(dictionary[1][2][3][4][5][6][7][8].key) + print(dictionary[1][2][3][4][5][6][7][8]["key"]) diff --git a/modules/gdscript/tests/scripts/parser/features/nested_dictionary.out b/modules/gdscript/tests/scripts/parser/features/nested_dictionary.out new file mode 100644 index 0000000000..4009160439 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_dictionary.out @@ -0,0 +1,5 @@ +GDTEST_OK +{8:{key:value}} +{key:value} +value +value diff --git a/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd new file mode 100644 index 0000000000..59cdc7d6c2 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd @@ -0,0 +1,5 @@ +func foo(x): + return x + 1 + +func test(): + print(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(0))))))))))))))))))))))))) diff --git a/modules/gdscript/tests/scripts/parser/features/nested_function_calls.out b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.out new file mode 100644 index 0000000000..28a6636a7b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.out @@ -0,0 +1,2 @@ +GDTEST_OK +24 diff --git a/modules/gdscript/tests/scripts/parser/features/nested_if.gd b/modules/gdscript/tests/scripts/parser/features/nested_if.gd new file mode 100644 index 0000000000..7282d08497 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_if.gd @@ -0,0 +1,57 @@ +func test(): + # 20 levels of nesting (and then some). + if true: + print("1") + if true: + print("2") + if true: + print("3") + if true: + print("4") + if true: + print("5") + if true: + print("6") + if true: + print("7") + if true: + print("8") + if true: + print("9") + if true: + print("10") + if true: + print("11") + if true: + print("12") + if true: + print("13") + if true: + print("14") + if true: + print("15") + if true: + print("16") + if true: + print("17") + if true: + print("18") + if true: + print("19") + if true: + print("20") + if false: + print("End") + if true: + if true: + if true: + if true: + if true: + if true: + if true: + if true: + if true: + if true: + if true: + if true: + print("This won't be printed") diff --git a/modules/gdscript/tests/scripts/parser/features/nested_if.out b/modules/gdscript/tests/scripts/parser/features/nested_if.out new file mode 100644 index 0000000000..c2d2e29a06 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_if.out @@ -0,0 +1,21 @@ +GDTEST_OK +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 diff --git a/modules/gdscript/tests/scripts/parser/features/nested_match.gd b/modules/gdscript/tests/scripts/parser/features/nested_match.gd new file mode 100644 index 0000000000..aaddcc7e83 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_match.gd @@ -0,0 +1,79 @@ +func test(): + # 20 levels of nesting (and then some). + var number = 1234 + match number: + 1234: + print("1") + match number: + 1234: + print("2") + match number: + 1234: + print("3") + continue + _: + print("Should also be printed") + match number: + 1234: + print("4") + match number: + _: + print("5") + match number: + false: + print("Should not be printed") + true: + print("Should not be printed") + "hello": + print("Should not be printed") + 1234: + print("6") + match number: + _: + print("7") + match number: + 1234: + print("8") + match number: + _: + print("9") + match number: + 1234: + print("10") + match number: + _: + print("11") + match number: + 1234: + print("12") + match number: + _: + print("13") + match number: + 1234: + print("14") + match number: + _: + print("15") + match number: + _: + print("16") + match number: + 1234: + print("17") + match number: + _: + print("18") + match number: + 1234: + print("19") + match number: + _: + print("20") + match number: + []: + print("Should not be printed") + _: + print("Should not be printed") + 5678: + print("Should not be printed either") diff --git a/modules/gdscript/tests/scripts/parser/features/nested_match.out b/modules/gdscript/tests/scripts/parser/features/nested_match.out new file mode 100644 index 0000000000..651d76cc59 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_match.out @@ -0,0 +1,22 @@ +GDTEST_OK +1 +2 +3 +Should also be printed +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 diff --git a/modules/gdscript/tests/scripts/parser/features/nested_parentheses.gd b/modules/gdscript/tests/scripts/parser/features/nested_parentheses.gd new file mode 100644 index 0000000000..3fef73b9be --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_parentheses.gd @@ -0,0 +1,65 @@ +func test(): + (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((print("Hello world!")))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) + + print(((((((((((((((((((((((((((((((((((((((((((((((((((((((((("Hello world 2!")))))))))))))))))))))))))))))))))))))))))))))))))))))))))) + + print( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + (2)) + ((4)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) diff --git a/modules/gdscript/tests/scripts/parser/features/nested_parentheses.out b/modules/gdscript/tests/scripts/parser/features/nested_parentheses.out new file mode 100644 index 0000000000..27221a56bb --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_parentheses.out @@ -0,0 +1,4 @@ +GDTEST_OK +Hello world! +Hello world 2! +6 diff --git a/modules/gdscript/tests/scripts/parser/features/number_separators.gd b/modules/gdscript/tests/scripts/parser/features/number_separators.gd new file mode 100644 index 0000000000..f5f5661cae --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/number_separators.gd @@ -0,0 +1,12 @@ +func test(): + # `_` can be used as a separator for numbers in GDScript. + # It can be placed anywhere in the number, except at the beginning. + # Currently, GDScript in the `master` branch only allows using one separator + # per number. + # Results are assigned to variables to avoid warnings. + var __ = 1_23 + __ = 123_ # Trailing number separators are OK. + __ = 12_3 + __ = 123_456 + __ = 0x1234_5678 + __ = 0b1001_0101 diff --git a/modules/gdscript/tests/scripts/parser/features/number_separators.out b/modules/gdscript/tests/scripts/parser/features/number_separators.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/number_separators.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/features/operator_assign.gd b/modules/gdscript/tests/scripts/parser/features/operator_assign.gd new file mode 100644 index 0000000000..b5f07675ca --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/operator_assign.gd @@ -0,0 +1,8 @@ +func test(): + var i = 0 + i += 5 + i -= 4 + i *= 10 + i %= 8 + i /= 0.25 + print(round(i)) diff --git a/modules/gdscript/tests/scripts/parser/features/operator_assign.out b/modules/gdscript/tests/scripts/parser/features/operator_assign.out new file mode 100644 index 0000000000..b0cb63ef59 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/operator_assign.out @@ -0,0 +1,2 @@ +GDTEST_OK +8 diff --git a/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd new file mode 100644 index 0000000000..9e4b360fb2 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd @@ -0,0 +1,37 @@ +# 4.0+ replacement for `setget`: +var _backing: int = 0 +var property: + get: + return _backing + 1000 + set(value): + _backing = value - 1000 + + +func test(): + print("Not using self:") + print(property) + print(_backing) + property = 5000 + print(property) + print(_backing) + _backing = -50 + print(property) + print(_backing) + property = 5000 + print(property) + print(_backing) + + # In Godot 4.0 and later, using `self` no longer makes a difference for + # getter/setter execution in GDScript. + print("Using self:") + print(self.property) + print(self._backing) + self.property = 5000 + print(self.property) + print(self._backing) + self._backing = -50 + print(self.property) + print(self._backing) + self.property = 5000 + print(self.property) + print(self._backing) diff --git a/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out new file mode 100644 index 0000000000..560e0c3bd7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out @@ -0,0 +1,19 @@ +GDTEST_OK +Not using self: +1000 +0 +5000 +4000 +950 +-50 +5000 +4000 +Using self: +5000 +4000 +5000 +4000 +950 +-50 +5000 +4000 diff --git a/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd index 08f2eedb2d..d50776c25c 100644 --- a/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd +++ b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd @@ -1,2 +1,5 @@ func test(): - print("A"); print("B") + print("A"); print("B") + + # Multiple semicolons and whitespace between them is also valid. + print("A"); ;;;;; ; print("B");; diff --git a/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out index fc03f3efe8..bd7f38f516 100644 --- a/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out +++ b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out @@ -1,3 +1,5 @@ GDTEST_OK A B +A +B diff --git a/modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.gd b/modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.gd new file mode 100644 index 0000000000..0f4aebb459 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.gd @@ -0,0 +1,22 @@ +#GDTEST_OK + +func test(): + a(); + b(); + c(); + d(); + e(); + +func a(): print("a"); + +func b(): print("b1"); print("b2") + +func c(): print("c1"); print("c2"); + +func d(): + print("d1"); + print("d2") + +func e(): + print("e1"); + print("e2"); diff --git a/modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.out b/modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.out new file mode 100644 index 0000000000..387cbd8fc2 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.out @@ -0,0 +1,10 @@ +GDTEST_OK +a +b1 +b2 +c1 +c2 +d1 +d2 +e1 +e2 diff --git a/modules/gdscript/tests/scripts/parser/features/signal_declaration.gd b/modules/gdscript/tests/scripts/parser/features/signal_declaration.gd new file mode 100644 index 0000000000..9ad98b78a8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/signal_declaration.gd @@ -0,0 +1,20 @@ +#GDTEST_OK + +# No parentheses. +signal a + +# No parameters. +signal b() + +# With paramters. +signal c(a, b, c) + +# With parameters multiline. +signal d( + a, + b, + c, +) + +func test(): + print("Ok") diff --git a/modules/gdscript/tests/scripts/parser/features/signal_declaration.out b/modules/gdscript/tests/scripts/parser/features/signal_declaration.out new file mode 100644 index 0000000000..0e9f482af4 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/signal_declaration.out @@ -0,0 +1,2 @@ +GDTEST_OK +Ok diff --git a/modules/gdscript/tests/scripts/parser/features/single_line_declaration.gd b/modules/gdscript/tests/scripts/parser/features/single_line_declaration.gd new file mode 100644 index 0000000000..650500663b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/single_line_declaration.gd @@ -0,0 +1,11 @@ +#GDTEST_OK + +func test(): C.new().test("Ok"); test2() + +func test2(): print("Ok 2") + +class A: pass + +class B extends RefCounted: pass + +class C extends RefCounted: func test(x): print(x) diff --git a/modules/gdscript/tests/scripts/parser/features/single_line_declaration.out b/modules/gdscript/tests/scripts/parser/features/single_line_declaration.out new file mode 100644 index 0000000000..e021923dc2 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/single_line_declaration.out @@ -0,0 +1,3 @@ +GDTEST_OK +Ok +Ok 2 diff --git a/modules/gdscript/tests/scripts/parser/features/space_indentation.gd b/modules/gdscript/tests/scripts/parser/features/space_indentation.gd new file mode 100644 index 0000000000..0a4887c199 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/space_indentation.gd @@ -0,0 +1,4 @@ +func test(): + # 2-space indentation should work, even though the GDScript style guide recommends tabs. + if true: + pass diff --git a/modules/gdscript/tests/scripts/parser/features/space_indentation.out b/modules/gdscript/tests/scripts/parser/features/space_indentation.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/space_indentation.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/features/static_typing.gd b/modules/gdscript/tests/scripts/parser/features/static_typing.gd new file mode 100644 index 0000000000..d42632c82d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/static_typing.gd @@ -0,0 +1,13 @@ +func test(): + # The following lines are equivalent: + var _integer: int = 1 + var _integer2 : int = 1 + var _inferred := 1 + var _inferred2 : = 1 + + # Type inference is automatic for constants. + const _INTEGER = 1 + const _INTEGER_REDUNDANT_TYPED : int = 1 + const _INTEGER_REDUNDANT_TYPED2 : int = 1 + const _INTEGER_REDUNDANT_INFERRED := 1 + const _INTEGER_REDUNDANT_INFERRED2 : = 1 diff --git a/modules/gdscript/tests/scripts/parser/features/static_typing.out b/modules/gdscript/tests/scripts/parser/features/static_typing.out new file mode 100644 index 0000000000..92ce7bc0e0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/static_typing.out @@ -0,0 +1,21 @@ +GDTEST_OK +>> WARNING +>> Line: 9 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_INTEGER' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER' +>> WARNING +>> Line: 10 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_INTEGER_REDUNDANT_TYPED' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER_REDUNDANT_TYPED' +>> WARNING +>> Line: 11 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_INTEGER_REDUNDANT_TYPED2' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER_REDUNDANT_TYPED2' +>> WARNING +>> Line: 12 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_INTEGER_REDUNDANT_INFERRED' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER_REDUNDANT_INFERRED' +>> WARNING +>> Line: 13 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_INTEGER_REDUNDANT_INFERRED2' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER_REDUNDANT_INFERRED2' diff --git a/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd new file mode 100644 index 0000000000..9e48a7f0da --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd @@ -0,0 +1,7 @@ +func test(): + var null_var = null + var true_var: bool = true + var false_var: bool = false + print(str(null_var)) + print(str(true_var)) + print(str(false_var)) diff --git a/modules/gdscript/tests/scripts/parser/features/str_preserves_case.out b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.out new file mode 100644 index 0000000000..abba38e87c --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.out @@ -0,0 +1,4 @@ +GDTEST_OK +null +true +false diff --git a/modules/gdscript/tests/scripts/parser/features/string_formatting.gd b/modules/gdscript/tests/scripts/parser/features/string_formatting.gd new file mode 100644 index 0000000000..a91837145d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/string_formatting.gd @@ -0,0 +1,18 @@ +func test(): + print("hello %s" % "world" == "hello world") + print("hello %s" % true == "hello true") + print("hello %s" % false == "hello false") + + print("hello %d" % 25 == "hello 25") + print("hello %d %d" % [25, 42] == "hello 25 42") + # Pad with spaces. + print("hello %3d" % 25 == "hello 25") + # Pad with zeroes. + print("hello %03d" % 25 == "hello 025") + + print("hello %.02f" % 0.123456 == "hello 0.12") + + # Dynamic padding: + # <https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/gdscript_format_string.html#dynamic-padding> + print("hello %*.*f" % [7, 3, 0.123456] == "hello 0.123") + print("hello %0*.*f" % [7, 3, 0.123456] == "hello 000.123") diff --git a/modules/gdscript/tests/scripts/parser/features/string_formatting.out b/modules/gdscript/tests/scripts/parser/features/string_formatting.out new file mode 100644 index 0000000000..7533f6ff54 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/string_formatting.out @@ -0,0 +1,11 @@ +GDTEST_OK +true +true +true +true +true +true +true +true +true +true diff --git a/modules/gdscript/tests/scripts/parser/features/super.gd b/modules/gdscript/tests/scripts/parser/features/super.gd new file mode 100644 index 0000000000..f5ae2a74a7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/super.gd @@ -0,0 +1,60 @@ +class Say: + var prefix = "S" + + func greet(): + prefix = "S Greeted" + print("hello") + + func say(name): + print(prefix, " say something ", name) + + +class SayAnotherThing extends Say: + # This currently crashes the engine. + #var prefix = "SAT" + + func greet(): + prefix = "SAT Greeted" + print("hi") + + func say(name): + print(prefix, " say another thing ", name) + + +class SayNothing extends Say: + # This currently crashes the engine. + #var prefix = "SN" + + func greet(): + super() + prefix = "SN Greeted" + print("howdy, see above") + + func greet_prefix_before_super(): + prefix = "SN Greeted" + super.greet() + print("howdy, see above") + + func say(name): + super(name + " super'd") + print(prefix, " say nothing... or not? ", name) + + +func test(): + var say = Say.new() + say.greet() + say.say("foo") + print() + + var say_another_thing = SayAnotherThing.new() + say_another_thing.greet() + say_another_thing.say("bar") + print() + + var say_nothing = SayNothing.new() + say_nothing.greet() + print(say_nothing.prefix) + say_nothing.greet_prefix_before_super() + print(say_nothing.prefix) + # This currently triggers a compiler bug: "compiler bug, function name not found" + #say_nothing.say("baz") diff --git a/modules/gdscript/tests/scripts/parser/features/super.out b/modules/gdscript/tests/scripts/parser/features/super.out new file mode 100644 index 0000000000..e0d4f4f098 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/super.out @@ -0,0 +1,13 @@ +GDTEST_OK +hello +S Greeted say something foo + +hi +SAT Greeted say another thing bar + +hello +howdy, see above +SN Greeted +hello +howdy, see above +S Greeted diff --git a/modules/gdscript/tests/scripts/parser/features/truthiness.gd b/modules/gdscript/tests/scripts/parser/features/truthiness.gd new file mode 100644 index 0000000000..9c67a152f5 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/truthiness.gd @@ -0,0 +1,30 @@ +func test(): + # The assertions below should all evaluate to `true` for this test to pass. + assert(true) + assert(not false) + assert(500) + assert(not 0) + assert(500.5) + assert(not 0.0) + assert("non-empty string") + assert(["non-empty array"]) + assert({"non-empty": "dictionary"}) + assert(Vector2(1, 0)) + assert(Vector2i(-1, -1)) + assert(Vector3(0, 0, 0.0001)) + assert(Vector3i(0, 0, 10000)) + + # Zero position is `true` only if the Rect2's size is non-zero. + assert(Rect2(0, 0, 0, 1)) + + # Zero size is `true` only if the position is non-zero. + assert(Rect2(1, 1, 0, 0)) + + # Zero position is `true` only if the Rect2's size is non-zero. + assert(Rect2i(0, 0, 0, 1)) + + # Zero size is `true` only if the position is non-zero. + assert(Rect2i(1, 1, 0, 0)) + + # A fully black color is only truthy if its alpha component is not equal to `1`. + assert(Color(0, 0, 0, 0.5)) diff --git a/modules/gdscript/tests/scripts/parser/features/truthiness.out b/modules/gdscript/tests/scripts/parser/features/truthiness.out new file mode 100644 index 0000000000..705524857b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/truthiness.out @@ -0,0 +1,65 @@ +GDTEST_OK +>> WARNING +>> Line: 3 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 4 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 5 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 6 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 7 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 8 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 9 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 12 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 13 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 14 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 15 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 18 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 21 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 24 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 27 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 30 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. diff --git a/modules/gdscript/tests/scripts/parser/features/typed_arrays.gd b/modules/gdscript/tests/scripts/parser/features/typed_arrays.gd new file mode 100644 index 0000000000..21bf3fdfcf --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/typed_arrays.gd @@ -0,0 +1,5 @@ +func test(): + var my_array: Array[int] = [1, 2, 3] + var inferred_array := [1, 2, 3] # This is Array[int]. + print(my_array) + print(inferred_array) diff --git a/modules/gdscript/tests/scripts/parser/features/typed_arrays.out b/modules/gdscript/tests/scripts/parser/features/typed_arrays.out new file mode 100644 index 0000000000..953d54d5e0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/typed_arrays.out @@ -0,0 +1,3 @@ +GDTEST_OK +[1, 2, 3] +[1, 2, 3] diff --git a/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd b/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd index 3b48f10ca7..65013c4301 100644 --- a/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd +++ b/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd @@ -1,12 +1,20 @@ -var a # No init. -var b = 42 # Init. +var m1 # No init. +var m2 = 22 # Init. +var m3: String # No init, typed. +var m4: String = "44" # Init, typed. + func test(): - var c # No init, local. - var d = 23 # Init, local. + var loc5 # No init, local. + var loc6 = 66 # Init, local. + var loc7: String # No init, typed. + var loc8: String = "88" # Init, typed. + + m1 = 11 + m3 = "33" - a = 1 - c = 2 + loc5 = 55 + loc7 = "77" - prints(a, b, c, d) + prints(m1, m2, m3, m4, loc5, loc6, loc7, loc8) print("OK") diff --git a/modules/gdscript/tests/scripts/parser/features/variable_declaration.out b/modules/gdscript/tests/scripts/parser/features/variable_declaration.out index 2e0a63c024..7817dd3169 100644 --- a/modules/gdscript/tests/scripts/parser/features/variable_declaration.out +++ b/modules/gdscript/tests/scripts/parser/features/variable_declaration.out @@ -1,7 +1,3 @@ GDTEST_OK ->> WARNING ->> Line: 5 ->> UNASSIGNED_VARIABLE ->> The variable 'c' was used but never assigned a value. -1 42 2 23 +11 22 33 44 55 66 77 88 OK diff --git a/modules/gdscript/tests/scripts/parser/features/while.gd b/modules/gdscript/tests/scripts/parser/features/while.gd new file mode 100644 index 0000000000..17dd4fbad2 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/while.gd @@ -0,0 +1,5 @@ +func test(): + var i = 0 + while i < 5: + print(i) + i += 1 diff --git a/modules/gdscript/tests/scripts/parser/features/while.out b/modules/gdscript/tests/scripts/parser/features/while.out new file mode 100644 index 0000000000..b4a50885c7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/while.out @@ -0,0 +1,6 @@ +GDTEST_OK +0 +1 +2 +3 +4 diff --git a/modules/gdscript/tests/scripts/parser/warnings/assert_always_true.gd b/modules/gdscript/tests/scripts/parser/warnings/assert_always_true.gd new file mode 100644 index 0000000000..8feaed899f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/assert_always_true.gd @@ -0,0 +1,5 @@ +func test(): + # These statements always evaluate to `true`, and therefore emit a warning. + assert(true) + assert(-1.234) + assert(2 + 3 == 5) diff --git a/modules/gdscript/tests/scripts/parser/warnings/assert_always_true.out b/modules/gdscript/tests/scripts/parser/warnings/assert_always_true.out new file mode 100644 index 0000000000..5132792cb7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/assert_always_true.out @@ -0,0 +1,13 @@ +GDTEST_OK +>> WARNING +>> Line: 3 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 4 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 5 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. diff --git a/modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.gd b/modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.gd new file mode 100644 index 0000000000..f72b10213f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.gd @@ -0,0 +1,8 @@ +func test(): + # `and` should be used instead. + if true && true: + pass + + # `or` should be used instead. + if false || true: + pass diff --git a/modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.out b/modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.gd new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.gd @@ -0,0 +1 @@ + diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.out new file mode 100644 index 0000000000..20eec212ba --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.out @@ -0,0 +1,4 @@ +>> WARNING +>> Line: 1 +>> EMPTY_FILE +>> Empty script file. diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.gd new file mode 100644 index 0000000000..15cd95ff2b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.gd @@ -0,0 +1 @@ +#a comment diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.out new file mode 100644 index 0000000000..20eec212ba --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.out @@ -0,0 +1,4 @@ +>> WARNING +>> Line: 1 +>> EMPTY_FILE +>> Empty script file. diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.gd new file mode 100644 index 0000000000..b28b04f643 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.gd @@ -0,0 +1,3 @@ + + + diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.out new file mode 100644 index 0000000000..20eec212ba --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.out @@ -0,0 +1,4 @@ +>> WARNING +>> Line: 1 +>> EMPTY_FILE +>> Empty script file. diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.gd new file mode 100644 index 0000000000..ecdba44d21 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.gd @@ -0,0 +1,4 @@ +#a comment, followed by a bunch of newlines + + + diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.out new file mode 100644 index 0000000000..20eec212ba --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.out @@ -0,0 +1,4 @@ +>> WARNING +>> Line: 1 +>> EMPTY_FILE +>> Empty script file. diff --git a/modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.gd b/modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.gd new file mode 100644 index 0000000000..a93ecb66b1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.gd @@ -0,0 +1,8 @@ +func test(): + # The ternary operator below returns values of different types and the + # result is assigned to a typed variable. This will cause a run-time error + # if the branch with the incompatible type is picked. Here, it won't happen + # since the `false` condition never evaluates to `true`. Instead, a warning + # will be emitted. + var __: int = 25 + __ = "hello" if false else -2 diff --git a/modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.out b/modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.out new file mode 100644 index 0000000000..7d1558c6fc --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 8 +>> INCOMPATIBLE_TERNARY +>> Values of the ternary conditional are not mutually compatible. diff --git a/modules/gdscript/tests/scripts/parser/warnings/integer_division.gd b/modules/gdscript/tests/scripts/parser/warnings/integer_division.gd new file mode 100644 index 0000000000..6117425528 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/integer_division.gd @@ -0,0 +1,10 @@ +func test(): + # This should emit a warning. + var __ = 5 / 2 + + # These should not emit warnings. + __ = float(5) / 2 + __ = 5 / float(2) + __ = 5.0 / 2 + __ = 5 / 2.0 + __ = 5.0 / 2.0 diff --git a/modules/gdscript/tests/scripts/parser/warnings/integer_division.out b/modules/gdscript/tests/scripts/parser/warnings/integer_division.out new file mode 100644 index 0000000000..40eb63ffcb --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/integer_division.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 3 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. diff --git a/modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.gd b/modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.gd new file mode 100644 index 0000000000..1eb54059dd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.gd @@ -0,0 +1,9 @@ +func test(): + var i = 25 + # The default branch (`_`) should be at the end of the `match` statement. + # Otherwise, a warning will be emitted + match i: + _: + print("default") + 25: + print("is 25") diff --git a/modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.out b/modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.out new file mode 100644 index 0000000000..8630fab420 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.out @@ -0,0 +1,6 @@ +GDTEST_OK +>> WARNING +>> Line: 8 +>> UNREACHABLE_PATTERN +>> Unreachable pattern (pattern after wildcard or bind). +default diff --git a/modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.gd b/modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.gd new file mode 100644 index 0000000000..954e697145 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.gd @@ -0,0 +1,5 @@ +func i_accept_ints_only(_i: int): + pass + +func test(): + i_accept_ints_only(12.345) diff --git a/modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.out b/modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.out new file mode 100644 index 0000000000..6fb592117b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> NARROWING_CONVERSION +>> Narrowing conversion (float is converted to int and loses precision). diff --git a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.gd b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.gd new file mode 100644 index 0000000000..00598e4d50 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.gd @@ -0,0 +1,6 @@ +func i_return_int() -> int: + return 4 + + +func test(): + i_return_int() diff --git a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.gd b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.gd new file mode 100644 index 0000000000..d565d38365 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.gd @@ -0,0 +1,8 @@ +# See also `parser-errors/redefine-class-constant.gd`. +const TEST = 25 + + +func test(): + # Warning here. This is not an error because a new constant is created, + # rather than attempting to set the value of an existing constant. + const TEST = 50 diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out new file mode 100644 index 0000000000..9c9417e11d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out @@ -0,0 +1,9 @@ +GDTEST_OK +>> WARNING +>> Line: 8 +>> UNUSED_LOCAL_CONSTANT +>> The local constant 'TEST' is declared but never used in the block. If this is intended, prefix it with an underscore: '_TEST' +>> WARNING +>> Line: 8 +>> SHADOWED_VARIABLE +>> The local constant "TEST" is shadowing an already-declared constant at line 2. diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.gd b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.gd new file mode 100644 index 0000000000..66dcf309e8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.gd @@ -0,0 +1,8 @@ +var foo = 123 + + +func test(): + # Notice the `var` keyword. Without this keyword, no warning would be emitted + # because no new variable would be created. Instead, the class variable's value + # would be overwritten. + var foo = 456 diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out new file mode 100644 index 0000000000..82e467b368 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out @@ -0,0 +1,9 @@ +GDTEST_OK +>> WARNING +>> Line: 8 +>> UNUSED_VARIABLE +>> The local variable 'foo' is declared but never used in the block. If this is intended, prefix it with an underscore: '_foo' +>> WARNING +>> Line: 8 +>> SHADOWED_VARIABLE +>> The local variable "foo" is shadowing an already-declared variable at line 1. diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.gd b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.gd new file mode 100644 index 0000000000..2c55d68be8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.gd @@ -0,0 +1,2 @@ +func test(): + var test = "This variable has the same name as the test() function." diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out new file mode 100644 index 0000000000..26ce0465b1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out @@ -0,0 +1,9 @@ +GDTEST_OK +>> WARNING +>> Line: 2 +>> UNUSED_VARIABLE +>> The local variable 'test' is declared but never used in the block. If this is intended, prefix it with an underscore: '_test' +>> WARNING +>> Line: 2 +>> SHADOWED_VARIABLE +>> The local variable "test" is shadowing an already-declared function at line 1. diff --git a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd new file mode 100644 index 0000000000..18ea260fa2 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd @@ -0,0 +1,9 @@ +func test(): + # The following statements should all be reported as standalone expressions: + "This is a standalone expression" + 1234 + 0.0 + 0.0 + Color(1, 1, 1) + Vector3.ZERO + [true, false] + float(125) diff --git a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out new file mode 100644 index 0000000000..99ec87438e --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out @@ -0,0 +1,21 @@ +GDTEST_OK +>> WARNING +>> Line: 3 +>> STANDALONE_EXPRESSION +>> Standalone expression (the line has no effect). +>> WARNING +>> Line: 4 +>> STANDALONE_EXPRESSION +>> Standalone expression (the line has no effect). +>> WARNING +>> Line: 5 +>> STANDALONE_EXPRESSION +>> Standalone expression (the line has no effect). +>> WARNING +>> Line: 7 +>> STANDALONE_EXPRESSION +>> Standalone expression (the line has no effect). +>> WARNING +>> Line: 8 +>> STANDALONE_EXPRESSION +>> Standalone expression (the line has no effect). diff --git a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.gd b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.gd new file mode 100644 index 0000000000..afb5059eea --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.gd @@ -0,0 +1,2 @@ +func test(): + var __ diff --git a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.out b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.out new file mode 100644 index 0000000000..cf14502e9a --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 2 +>> UNASSIGNED_VARIABLE +>> The variable '__' was used but never assigned a value. diff --git a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.gd b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.gd new file mode 100644 index 0000000000..d77791f4c5 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.gd @@ -0,0 +1,4 @@ +func test(): + var __: int + # Variable has no set value at this point (even though it's implicitly `0` here). + __ += 15 diff --git a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.out b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.out new file mode 100644 index 0000000000..ba55a4e0f8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 4 +>> UNASSIGNED_VARIABLE_OP_ASSIGN +>> Using assignment with operation but the variable '__' was not previously assigned a value. diff --git a/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.gd b/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.gd new file mode 100644 index 0000000000..3311f342ab --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.gd @@ -0,0 +1,7 @@ +func test(): + var i = 25 + + return + + # This will never be run due to the `return` statement above. + print(i) diff --git a/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.out b/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.out new file mode 100644 index 0000000000..9316abd5eb --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 7 +>> UNREACHABLE_CODE +>> Unreachable code (statement after return) in function 'test()'. diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_argument.gd b/modules/gdscript/tests/scripts/parser/warnings/unused_argument.gd new file mode 100644 index 0000000000..e6e24dc6f2 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unused_argument.gd @@ -0,0 +1,12 @@ +# This should emit a warning since the unused argument is not prefixed with an underscore. +func function_with_unused_argument(p_arg1, p_arg2): + print(p_arg1) + + +# This shouldn't emit a warning since the unused argument is prefixed with an underscore. +func function_with_ignored_unused_argument(p_arg1, _p_arg2): + print(p_arg1) + + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_argument.out b/modules/gdscript/tests/scripts/parser/warnings/unused_argument.out new file mode 100644 index 0000000000..92f3308f85 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unused_argument.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 2 +>> UNUSED_PARAMETER +>> The parameter 'p_arg2' is never used in the function 'function_with_unused_argument'. If this is intended, prefix it with an underscore: '_p_arg2' diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd index 68e3bd424f..013a2e4beb 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd +++ b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd @@ -1,2 +1,4 @@ func test(): - var unused = "not used" + var unused = "not used" + + var _unused = "not used, but no warning since the variable name starts with an underscore" diff --git a/modules/gdscript/tests/scripts/parser/warnings/void_assignment.gd b/modules/gdscript/tests/scripts/parser/warnings/void_assignment.gd new file mode 100644 index 0000000000..b4a42b3e3d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/void_assignment.gd @@ -0,0 +1,6 @@ +func i_return_void() -> void: + return + + +func test(): + var __ = i_return_void() diff --git a/modules/gdscript/tests/scripts/parser/warnings/void_assignment.out b/modules/gdscript/tests/scripts/parser/warnings/void_assignment.out new file mode 100644 index 0000000000..84c9598f9a --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/void_assignment.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 6 +>> VOID_ASSIGNMENT +>> Assignment operation, but the function 'i_return_void()' returns void. diff --git a/modules/gdscript/tests/scripts/runtime/features/assign_member_with_operation.gd b/modules/gdscript/tests/scripts/runtime/features/assign_member_with_operation.gd new file mode 100644 index 0000000000..f6526aefb4 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/assign_member_with_operation.gd @@ -0,0 +1,13 @@ +extends Node + +func test(): + process_priority = 10 + var change = 20 + + print(process_priority) + print(change) + + process_priority += change + + print(process_priority) + print(change) diff --git a/modules/gdscript/tests/scripts/runtime/features/assign_member_with_operation.out b/modules/gdscript/tests/scripts/runtime/features/assign_member_with_operation.out new file mode 100644 index 0000000000..c9e6b34c77 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/assign_member_with_operation.out @@ -0,0 +1,5 @@ +GDTEST_OK +10 +20 +30 +20 diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd new file mode 100644 index 0000000000..c6645c2c34 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd @@ -0,0 +1,138 @@ +func test(): + var value + + # null + value = null + print(value == null) + + # bool + value = false + print(value == null) + + # int + value = 0 + print(value == null) + + # float + value = 0.0 + print(value == null) + + # String + value = "" + print(value == null) + + # Vector2 + value = Vector2() + print(value == null) + + # Vector2i + value = Vector2i() + print(value == null) + + # Rect2 + value = Rect2() + print(value == null) + + # Rect2i + value = Rect2i() + print(value == null) + + # Vector3 + value = Vector3() + print(value == null) + + # Vector3i + value = Vector3i() + print(value == null) + + # Transform2D + value = Transform2D() + print(value == null) + + # Plane + value = Plane() + print(value == null) + + # Quaternion + value = Quaternion() + print(value == null) + + # AABB + value = AABB() + print(value == null) + + # Basis + value = Basis() + print(value == null) + + # Transform3D + value = Transform3D() + print(value == null) + + # Color + value = Color() + print(value == null) + + # StringName + value = &"" + print(value == null) + + # NodePath + value = ^"" + print(value == null) + + # RID + value = RID() + print(value == null) + + # Callable + value = Callable() + print(value == null) + + # Signal + value = Signal() + print(value == null) + + # Dictionary + value = {} + print(value == null) + + # Array + value = [] + print(value == null) + + # PackedByteArray + value = PackedByteArray() + print(value == null) + + # PackedInt32Array + value = PackedInt32Array() + print(value == null) + + # PackedInt64Array + value = PackedInt64Array() + print(value == null) + + # PackedFloat32Array + value = PackedFloat32Array() + print(value == null) + + # PackedFloat64Array + value = PackedFloat64Array() + print(value == null) + + # PackedStringArray + value = PackedStringArray() + print(value == null) + + # PackedVector2Array + value = PackedVector2Array() + print(value == null) + + # PackedVector3Array + value = PackedVector3Array() + print(value == null) + + # PackedColorArray + value = PackedColorArray() + print(value == null) diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out new file mode 100644 index 0000000000..639f6027b9 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out @@ -0,0 +1,35 @@ +GDTEST_OK +true +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd new file mode 100644 index 0000000000..ee622bf22f --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd @@ -0,0 +1,138 @@ +func test(): + var value + + # null + value = null + print(value != null) + + # bool + value = false + print(value != null) + + # int + value = 0 + print(value != null) + + # float + value = 0.0 + print(value != null) + + # String + value = "" + print(value != null) + + # Vector2 + value = Vector2() + print(value != null) + + # Vector2i + value = Vector2i() + print(value != null) + + # Rect2 + value = Rect2() + print(value != null) + + # Rect2i + value = Rect2i() + print(value != null) + + # Vector3 + value = Vector3() + print(value != null) + + # Vector3i + value = Vector3i() + print(value != null) + + # Transform2D + value = Transform2D() + print(value != null) + + # Plane + value = Plane() + print(value != null) + + # Quaternion + value = Quaternion() + print(value != null) + + # AABB + value = AABB() + print(value != null) + + # Basis + value = Basis() + print(value != null) + + # Transform3D + value = Transform3D() + print(value != null) + + # Color + value = Color() + print(value != null) + + # StringName + value = &"" + print(value != null) + + # NodePath + value = ^"" + print(value != null) + + # RID + value = RID() + print(value != null) + + # Callable + value = Callable() + print(value != null) + + # Signal + value = Signal() + print(value != null) + + # Dictionary + value = {} + print(value != null) + + # Array + value = [] + print(value != null) + + # PackedByteArray + value = PackedByteArray() + print(value != null) + + # PackedInt32Array + value = PackedInt32Array() + print(value != null) + + # PackedInt64Array + value = PackedInt64Array() + print(value != null) + + # PackedFloat32Array + value = PackedFloat32Array() + print(value != null) + + # PackedFloat64Array + value = PackedFloat64Array() + print(value != null) + + # PackedStringArray + value = PackedStringArray() + print(value != null) + + # PackedVector2Array + value = PackedVector2Array() + print(value != null) + + # PackedVector3Array + value = PackedVector3Array() + print(value != null) + + # PackedColorArray + value = PackedColorArray() + print(value != null) diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out new file mode 100644 index 0000000000..d1e332afba --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out @@ -0,0 +1,35 @@ +GDTEST_OK +false +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.gd b/modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.gd new file mode 100644 index 0000000000..7649062fda --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.gd @@ -0,0 +1,138 @@ +func test(): + var value + + # null + value = null + print(null == value) + + # bool + value = false + print(null == value) + + # int + value = 0 + print(null == value) + + # float + value = 0.0 + print(null == value) + + # String + value = "" + print(null == value) + + # Vector2 + value = Vector2() + print(null == value) + + # Vector2i + value = Vector2i() + print(null == value) + + # Rect2 + value = Rect2() + print(null == value) + + # Rect2i + value = Rect2i() + print(null == value) + + # Vector3 + value = Vector3() + print(null == value) + + # Vector3i + value = Vector3i() + print(null == value) + + # Transform2D + value = Transform2D() + print(null == value) + + # Plane + value = Plane() + print(null == value) + + # Quaternion + value = Quaternion() + print(null == value) + + # AABB + value = AABB() + print(null == value) + + # Basis + value = Basis() + print(null == value) + + # Transform3D + value = Transform3D() + print(null == value) + + # Color + value = Color() + print(null == value) + + # StringName + value = &"" + print(null == value) + + # NodePath + value = ^"" + print(null == value) + + # RID + value = RID() + print(null == value) + + # Callable + value = Callable() + print(null == value) + + # Signal + value = Signal() + print(null == value) + + # Dictionary + value = {} + print(null == value) + + # Array + value = [] + print(null == value) + + # PackedByteArray + value = PackedByteArray() + print(null == value) + + # PackedInt32Array + value = PackedInt32Array() + print(null == value) + + # PackedInt64Array + value = PackedInt64Array() + print(null == value) + + # PackedFloat32Array + value = PackedFloat32Array() + print(null == value) + + # PackedFloat64Array + value = PackedFloat64Array() + print(null == value) + + # PackedStringArray + value = PackedStringArray() + print(null == value) + + # PackedVector2Array + value = PackedVector2Array() + print(null == value) + + # PackedVector3Array + value = PackedVector3Array() + print(null == value) + + # PackedColorArray + value = PackedColorArray() + print(null == value) diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.out b/modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.out new file mode 100644 index 0000000000..639f6027b9 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.out @@ -0,0 +1,35 @@ +GDTEST_OK +true +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.gd b/modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.gd new file mode 100644 index 0000000000..8d5f9df1b8 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.gd @@ -0,0 +1,138 @@ +func test(): + var value + + # null + value = null + print(null != value) + + # bool + value = false + print(null != value) + + # int + value = 0 + print(null != value) + + # float + value = 0.0 + print(null != value) + + # String + value = "" + print(null != value) + + # Vector2 + value = Vector2() + print(null != value) + + # Vector2i + value = Vector2i() + print(null != value) + + # Rect2 + value = Rect2() + print(null != value) + + # Rect2i + value = Rect2i() + print(null != value) + + # Vector3 + value = Vector3() + print(null != value) + + # Vector3i + value = Vector3i() + print(null != value) + + # Transform2D + value = Transform2D() + print(null != value) + + # Plane + value = Plane() + print(null != value) + + # Quaternion + value = Quaternion() + print(null != value) + + # AABB + value = AABB() + print(null != value) + + # Basis + value = Basis() + print(null != value) + + # Transform3D + value = Transform3D() + print(null != value) + + # Color + value = Color() + print(null != value) + + # StringName + value = &"" + print(null != value) + + # NodePath + value = ^"" + print(null != value) + + # RID + value = RID() + print(null != value) + + # Callable + value = Callable() + print(null != value) + + # Signal + value = Signal() + print(null != value) + + # Dictionary + value = {} + print(null != value) + + # Array + value = [] + print(null != value) + + # PackedByteArray + value = PackedByteArray() + print(null != value) + + # PackedInt32Array + value = PackedInt32Array() + print(null != value) + + # PackedInt64Array + value = PackedInt64Array() + print(null != value) + + # PackedFloat32Array + value = PackedFloat32Array() + print(null != value) + + # PackedFloat64Array + value = PackedFloat64Array() + print(null != value) + + # PackedStringArray + value = PackedStringArray() + print(null != value) + + # PackedVector2Array + value = PackedVector2Array() + print(null != value) + + # PackedVector3Array + value = PackedVector3Array() + print(null != value) + + # PackedColorArray + value = PackedColorArray() + print(null != value) diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.out b/modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.out new file mode 100644 index 0000000000..d1e332afba --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.out @@ -0,0 +1,35 @@ +GDTEST_OK +false +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true diff --git a/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.gd b/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.gd new file mode 100644 index 0000000000..3eb02816ed --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.gd @@ -0,0 +1,11 @@ +#GDTEST_OK +var prop : int = 0: + get: + return prop + set(value): + prop = value % 7 + +func test(): + for i in 7: + prop += 1 + print(prop) diff --git a/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.out b/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.out new file mode 100644 index 0000000000..76157853f2 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.out @@ -0,0 +1,8 @@ +GDTEST_OK +1 +2 +3 +4 +5 +6 +0 diff --git a/modules/gdscript/tests/scripts/runtime/features/recursion.gd b/modules/gdscript/tests/scripts/runtime/features/recursion.gd new file mode 100644 index 0000000000..a35485022e --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/recursion.gd @@ -0,0 +1,19 @@ +func is_prime(number: int, divisor: int = 2) -> bool: + print(divisor) + if number <= 2: + return (number == 2) + elif number % divisor == 0: + return false + elif divisor * divisor > number: + return true + + return is_prime(number, divisor + 1) + +func test(): + # Not a prime number. + print(is_prime(989)) + + print() + + # Largest prime number below 10000. + print(is_prime(9973)) diff --git a/modules/gdscript/tests/scripts/runtime/features/recursion.out b/modules/gdscript/tests/scripts/runtime/features/recursion.out new file mode 100644 index 0000000000..2bd8f24988 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/recursion.out @@ -0,0 +1,125 @@ +GDTEST_OK +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +false + +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +true diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp index 52e9d92223..80eabc1596 100644 --- a/modules/gdscript/tests/test_gdscript.cpp +++ b/modules/gdscript/tests/test_gdscript.cpp @@ -56,7 +56,7 @@ static void test_tokenizer(const String &p_code, const Vector<String> &p_lines) int tab_size = 4; #ifdef TOOLS_ENABLED if (EditorSettings::get_singleton()) { - tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size"); + tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size"); } #endif // TOOLS_ENABLED String tab = String(" ").repeat(tab_size); @@ -172,8 +172,8 @@ static void test_compiler(const String &p_code, const String &p_script_path, con return; } - for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) { - const GDScriptFunction *func = E->value(); + for (const KeyValue<StringName, GDScriptFunction *> &E : script->get_member_functions()) { + const GDScriptFunction *func = E.value; String signature = "Disassembling " + func->get_name().operator String() + "("; for (int i = 0; i < func->get_argument_count(); i++) { |