diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 822fc412b44..cf60a975dd2 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -976,6 +976,7 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio } } + // TODO: Unify with _find_identifiers_in_class if (p_context.current_class) { if (!p_inherit_only && p_context.current_class->base_type.is_set()) { // Native enums from base class @@ -987,24 +988,26 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio } } // Check current class for potential types + // TODO: Also check classes the current class inherits from. const GDScriptParser::ClassNode *current = p_context.current_class; + int location_offset = 0; while (current) { for (int i = 0; i < current->members.size(); i++) { const GDScriptParser::ClassNode::Member &member = current->members[i]; switch (member.type) { case GDScriptParser::ClassNode::Member::CLASS: { - ScriptLanguage::CodeCompletionOption option(member.m_class->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_LOCAL); + ScriptLanguage::CodeCompletionOption option(member.m_class->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_LOCAL + location_offset); r_result.insert(option.display, option); } break; case GDScriptParser::ClassNode::Member::ENUM: { if (!p_inherit_only) { - ScriptLanguage::CodeCompletionOption option(member.m_enum->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_ENUM, ScriptLanguage::LOCATION_LOCAL); + ScriptLanguage::CodeCompletionOption option(member.m_enum->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_ENUM, ScriptLanguage::LOCATION_LOCAL + location_offset); r_result.insert(option.display, option); } } break; case GDScriptParser::ClassNode::Member::CONSTANT: { - if (member.constant->get_datatype().is_meta_type && p_context.current_class->outer != nullptr) { - ScriptLanguage::CodeCompletionOption option(member.constant->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_LOCAL); + if (member.constant->get_datatype().is_meta_type) { + ScriptLanguage::CodeCompletionOption option(member.constant->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_LOCAL + location_offset); r_result.insert(option.display, option); } } break; @@ -1012,6 +1015,7 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio break; } } + location_offset += 1; current = current->outer; } } @@ -1076,7 +1080,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, option = ScriptLanguage::CodeCompletionOption(member.variable->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); break; case GDScriptParser::ClassNode::Member::CONSTANT: - if (p_types_only || p_only_functions) { + if ((p_types_only && !member.constant->datatype.is_meta_type) || p_only_functions) { continue; } if (r_result.has(member.constant->identifier->name)) { @@ -1308,6 +1312,10 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base return; } break; case GDScriptParser::DataType::ENUM: { + if (p_types_only) { + return; + } + String type_str = base_type.native_type; StringName type = type_str.get_slicec('.', 0); StringName type_enum = base_type.enum_type; @@ -2489,6 +2497,16 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & return true; } } + if (ClassDB::has_enum(class_name, p_identifier)) { + r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_type.type.kind = GDScriptParser::DataType::ENUM; + r_type.type.enum_type = p_identifier; + r_type.type.is_constant = true; + r_type.type.is_meta_type = true; + r_type.type.native_type = String(class_name) + "." + p_identifier; + return true; + } + return false; } break; case GDScriptParser::DataType::BUILTIN: { @@ -3313,44 +3331,27 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c if (!completion_context.current_class) { break; } + const GDScriptParser::TypeNode *type = static_cast(completion_context.node); - bool found = true; + ERR_FAIL_INDEX_V_MSG(completion_context.type_chain_index - 1, type->type_chain.size(), Error::ERR_BUG, "Could not complete type argument with out of bounds type chain index."); GDScriptCompletionIdentifier base; - base.type.kind = GDScriptParser::DataType::CLASS; - base.type.type_source = GDScriptParser::DataType::INFERRED; - base.type.is_constant = true; - if (completion_context.current_argument == 1) { - StringName type_name = type->type_chain[0]->name; - - if (ClassDB::class_exists(type_name)) { - base.type.kind = GDScriptParser::DataType::NATIVE; - base.type.native_type = type_name; - } else if (ScriptServer::is_global_class(type_name)) { - base.type.kind = GDScriptParser::DataType::SCRIPT; - String scr_path = ScriptServer::get_global_class_path(type_name); - base.type.script_type = ResourceLoader::load(scr_path); - } - } - - if (base.type.kind == GDScriptParser::DataType::CLASS) { - base.type.class_type = completion_context.current_class; - base.value = completion_context.base; - - for (int i = 0; i < completion_context.current_argument; i++) { + if (_guess_identifier_type(completion_context, type->type_chain[0], base)) { + bool found = true; + for (int i = 1; i < completion_context.type_chain_index; i++) { GDScriptCompletionIdentifier ci; - if (!_guess_identifier_type_from_base(completion_context, base, type->type_chain[i]->name, ci)) { - found = false; + found = _guess_identifier_type_from_base(completion_context, base, type->type_chain[i]->name, ci); + base = ci; + if (!found) { break; } - base = ci; + } + if (found) { + _find_identifiers_in_base(base, false, true, options, 0); } } - if (found) { - _find_identifiers_in_base(base, false, true, options, 0); - } r_forced = true; } break; case GDScriptParser::COMPLETION_RESOURCE_PATH: { diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 60ee4776566..65700a56768 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1310,7 +1310,10 @@ public: FunctionNode *current_function = nullptr; SuiteNode *current_suite = nullptr; int current_line = -1; - int current_argument = -1; + union { + int current_argument = -1; + int type_chain_index; + }; Variant::Type builtin_type = Variant::VARIANT_MAX; Node *node = nullptr; Object *base = nullptr; diff --git a/modules/gdscript/tests/scripts/completion/class_a.notest.gd b/modules/gdscript/tests/scripts/completion/class_a.notest.gd index 47c64dc674a..aa311c15499 100644 --- a/modules/gdscript/tests/scripts/completion/class_a.notest.gd +++ b/modules/gdscript/tests/scripts/completion/class_a.notest.gd @@ -1,5 +1,27 @@ extends Node +class InnerA: + class InnerInnerA: + enum EnumOfInnerInnerA { + ENUM_VALUE_1, + ENUM_VALUE_2, + } + + enum EnumOfInnerA { + ENUM_VALUE_1, + ENUM_VALUE_2, + } + + signal signal_of_inner_a + var property_of_inner_a + func func_of_inner_a(): + pass + +enum EnumOfA { + ENUM_VALUE_1, + ENUM_VALUE_2, +} + signal signal_of_a var property_of_a diff --git a/modules/gdscript/tests/scripts/completion/class_b.notest.gd b/modules/gdscript/tests/scripts/completion/class_b.notest.gd new file mode 100644 index 00000000000..883f9d3e285 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/class_b.notest.gd @@ -0,0 +1,31 @@ +extends Node +class_name B + +class InnerB: + class InnerInnerB: + enum EnumOfInnerInnerB { + ENUM_VALUE_1, + ENUM_VALUE_2, + } + + enum EnumOfInnerB { + ENUM_VALUE_1, + ENUM_VALUE_2, + } + + signal signal_of_inner_b + var property_of_inner_b + func func_of_inner_b(): + pass + +enum EnumOfB { + ENUM_VALUE_1, + ENUM_VALUE_2, +} + +signal signal_of_b + +var property_of_b + +func func_of_b(): + pass diff --git a/modules/gdscript/tests/scripts/completion/types/hints/index_0.cfg b/modules/gdscript/tests/scripts/completion/types/hints/index_0.cfg new file mode 100644 index 00000000000..9fa29fad172 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/hints/index_0.cfg @@ -0,0 +1,21 @@ +[output] +include=[ + {"display": "A"}, + {"display": "B"}, + {"display": "LocalInnerClass"}, + {"display": "LocalInnerEnum"}, + {"display": "ConnectFlags"}, + {"display": "int"}, + {"display": "String"}, + {"display": "float"}, + {"display": "Vector2"}, + {"display": "Vector3"}, + {"display": "Vector4"}, + {"display": "Node"}, + {"display": "Node2D"}, +] +exclude=[ + {"display": "AInner"}, + {"display": "LocalInnerInnerEnum"}, + {"display": "LocalInnerInnerClass"}, +] diff --git a/modules/gdscript/tests/scripts/completion/types/hints/index_0.gd b/modules/gdscript/tests/scripts/completion/types/hints/index_0.gd new file mode 100644 index 00000000000..350369deae5 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/hints/index_0.gd @@ -0,0 +1,11 @@ +const A = preload("res://completion/class_a.notest.gd") + +class LocalInnerClass: + const AInner = preload("res://completion/class_a.notest.gd") + enum LocalInnerInnerEnum {} + class LocalInnerInnerClass: + pass + +enum LocalInnerEnum {} + +var test_var: A➡ diff --git a/modules/gdscript/tests/scripts/completion/types/hints/index_0_inner_class.cfg b/modules/gdscript/tests/scripts/completion/types/hints/index_0_inner_class.cfg new file mode 100644 index 00000000000..b804df22636 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/hints/index_0_inner_class.cfg @@ -0,0 +1,19 @@ +[output] +include=[ + {"display": "A"}, + {"display": "AInner"}, + {"display": "B"}, + {"display": "LocalInnerClass"}, + {"display": "LocalInnerInnerClass"}, + {"display": "LocalInnerEnum"}, + {"display": "LocalInnerInnerEnum"}, + {"display": "ConnectFlags"}, + {"display": "int"}, + {"display": "String"}, + {"display": "float"}, + {"display": "Vector2"}, + {"display": "Vector3"}, + {"display": "Vector4"}, + {"display": "Node"}, + {"display": "Node2D"}, +] diff --git a/modules/gdscript/tests/scripts/completion/types/hints/index_0_inner_class.gd b/modules/gdscript/tests/scripts/completion/types/hints/index_0_inner_class.gd new file mode 100644 index 00000000000..2bf95ad8e72 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/hints/index_0_inner_class.gd @@ -0,0 +1,10 @@ +const A = preload("res://completion/class_a.notest.gd") + +class LocalInnerClass: + const AInner = preload("res://completion/class_a.notest.gd") + enum LocalInnerInnerEnum {} + class LocalInnerInnerClass: + pass + var test_var: A➡ + +enum LocalInnerEnum {} diff --git a/modules/gdscript/tests/scripts/completion/types/hints/index_1_global_class.cfg b/modules/gdscript/tests/scripts/completion/types/hints/index_1_global_class.cfg new file mode 100644 index 00000000000..6e658d68008 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/hints/index_1_global_class.cfg @@ -0,0 +1,12 @@ +[output] +include=[ + {"display": "InnerB"}, + {"display": "EnumOfB"}, + {"display": "ConnectFlags"}, +] +exclude=[ + {"display": "int"}, + {"display": "String"}, + {"display": "Node2D"}, + {"display": "B"}, +] diff --git a/modules/gdscript/tests/scripts/completion/types/hints/index_1_global_class.gd b/modules/gdscript/tests/scripts/completion/types/hints/index_1_global_class.gd new file mode 100644 index 00000000000..631b3dc998f --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/hints/index_1_global_class.gd @@ -0,0 +1 @@ +var test_var: B.➡ diff --git a/modules/gdscript/tests/scripts/completion/types/hints/index_1_local_class.cfg b/modules/gdscript/tests/scripts/completion/types/hints/index_1_local_class.cfg new file mode 100644 index 00000000000..cbec7c33e64 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/hints/index_1_local_class.cfg @@ -0,0 +1,12 @@ +[output] +include=[ + {"display": "InnerInnerClass"}, + {"display": "InnerInnerEnum"}, + {"display": "ConnectFlags"}, +] +exclude=[ + {"display": "int"}, + {"display": "String"}, + {"display": "Node2D"}, + {"display": "B"}, +] diff --git a/modules/gdscript/tests/scripts/completion/types/hints/index_1_local_class.gd b/modules/gdscript/tests/scripts/completion/types/hints/index_1_local_class.gd new file mode 100644 index 00000000000..fcd24518031 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/hints/index_1_local_class.gd @@ -0,0 +1,8 @@ +const A = preload("res://completion/class_a.notest.gd") + +class LocalInnerClass: + class InnerInnerClass: + pass + enum InnerInnerEnum {} + +var test_var: LocalInnerClass.➡ diff --git a/modules/gdscript/tests/scripts/completion/types/hints/index_1_local_enum.cfg b/modules/gdscript/tests/scripts/completion/types/hints/index_1_local_enum.cfg new file mode 100644 index 00000000000..cd57a2615ae --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/hints/index_1_local_enum.cfg @@ -0,0 +1,11 @@ +[output] +exclude=[ + {"display": "TEST_LOCAL_VAL"}, + {"display": "ConnectFlags"}, +] +exclude=[ + {"display": "int"}, + {"display": "String"}, + {"display": "Node2D"}, + {"display": "B"}, +] diff --git a/modules/gdscript/tests/scripts/completion/types/hints/index_1_local_enum.gd b/modules/gdscript/tests/scripts/completion/types/hints/index_1_local_enum.gd new file mode 100644 index 00000000000..8e4c6e58e83 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/hints/index_1_local_enum.gd @@ -0,0 +1,7 @@ +const A = preload("res://completion/class_a.notest.gd") + +enum LocalInnerEnum { + TEST_LOCAL_VAL, +} + +var test_var: LocalInnerEnum.➡ diff --git a/modules/gdscript/tests/scripts/completion/types/hints/index_1_preload.cfg b/modules/gdscript/tests/scripts/completion/types/hints/index_1_preload.cfg new file mode 100644 index 00000000000..1ccf955bab9 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/hints/index_1_preload.cfg @@ -0,0 +1,12 @@ +[output] +include=[ + {"display": "InnerA"}, + {"display": "EnumOfA"}, + {"display": "ConnectFlags"}, +] +exclude=[ + {"display": "int"}, + {"display": "String"}, + {"display": "Node2D"}, + {"display": "B"}, +] diff --git a/modules/gdscript/tests/scripts/completion/types/hints/index_1_preload.gd b/modules/gdscript/tests/scripts/completion/types/hints/index_1_preload.gd new file mode 100644 index 00000000000..56f8d2ec7bf --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/hints/index_1_preload.gd @@ -0,0 +1,3 @@ +const A = preload("res://completion/class_a.notest.gd") + +var test_var: A.➡ diff --git a/modules/gdscript/tests/scripts/completion/types/hints/index_2.cfg b/modules/gdscript/tests/scripts/completion/types/hints/index_2.cfg new file mode 100644 index 00000000000..4f7e06c822d --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/hints/index_2.cfg @@ -0,0 +1,19 @@ +[output] +include=[ + {"display": "AInnerInner"}, + {"display": "InnerInnerInnerEnum"}, + {"display": "InnerInnerInnerClass"}, + {"display": "ConnectFlags"}, +] +exclude=[ + {"display": "A"}, + {"display": "AInner"}, + {"display": "TestEnum"}, + {"display": "InnerInnerEnum"}, + {"display": "InnerInnerClass"}, + {"display": "LocalInnerClass"}, + {"display": "int"}, + {"display": "String"}, + {"display": "Node2D"}, + {"display": "B"}, +] diff --git a/modules/gdscript/tests/scripts/completion/types/hints/index_2.gd b/modules/gdscript/tests/scripts/completion/types/hints/index_2.gd new file mode 100644 index 00000000000..5d19ce72c88 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/hints/index_2.gd @@ -0,0 +1,14 @@ +const A = preload("res://completion/class_a.notest.gd") + +class LocalInnerClass: + const AInner = preload("res://completion/class_a.notest.gd") + class InnerInnerClass: + const AInnerInner = preload("res://completion/class_a.notest.gd") + enum InnerInnerInnerEnum {} + class InnerInnerInnerClass: + pass + enum InnerInnerEnum {} + +enum TestEnum {} + +var test_var: LocalInnerClass.InnerInnerClass.➡ diff --git a/modules/gdscript/tests/test_completion.h b/modules/gdscript/tests/test_completion.h index 387358934d8..cb18d90c60c 100644 --- a/modules/gdscript/tests/test_completion.h +++ b/modules/gdscript/tests/test_completion.h @@ -160,6 +160,7 @@ static void test_directory(const String &p_dir) { owner = scene->get_node(conf.get_value("input", "node_path", ".")); } + ERR_PRINT_OFF if (owner != nullptr) { // Remove the line which contains the sentinel char, to get a valid script. Ref scr; @@ -181,8 +182,9 @@ static void test_directory(const String &p_dir) { scr->set_path(res_path); owner->set_script(scr); } - GDScriptLanguage::get_singleton()->complete_code(code, res_path, owner, &options, forced, call_hint); + ERR_PRINT_ON + String contains_excluded; for (ScriptLanguage::CodeCompletionOption &option : options) { for (const Dictionary &E : exclude) { @@ -219,13 +221,50 @@ static void test_directory(const String &p_dir) { } } +static void setup_global_classes(const String &p_dir) { + Error err = OK; + Ref dir = DirAccess::open(p_dir, &err); + + if (err != OK) { + FAIL("Invalid test directory."); + return; + } + + String path = dir->get_current_dir(); + + dir->list_dir_begin(); + String next = dir->get_next(); + + while (!next.is_empty()) { + if (dir->current_is_dir() && next != "." && next != "..") { + setup_global_classes(path.path_join(next)); + } else if (next.ends_with(".gd")) { + String base_type; + String source_file = path.path_join(next); + String class_name = GDScriptLanguage::get_singleton()->get_global_class_name(source_file, &base_type); + if (class_name.is_empty()) { + next = dir->get_next(); + continue; + } + ERR_FAIL_COND_MSG(ScriptServer::is_global_class(class_name), + "Class name '" + class_name + "' from " + source_file + " is already used in " + ScriptServer::get_global_class_path(class_name)); + + ScriptServer::add_global_class(class_name, base_type, GDScriptLanguage::get_singleton()->get_name(), source_file); + } + next = dir->get_next(); + } +} + TEST_SUITE("[Modules][GDScript][Completion]") { TEST_CASE("[Editor] Check suggestion list") { // Set all editor settings that code completion relies on. EditorSettings::get_singleton()->set_setting("text_editor/completion/use_single_quotes", false); init_language("modules/gdscript/tests/scripts"); + setup_global_classes("modules/gdscript/tests/scripts/completion"); test_directory("modules/gdscript/tests/scripts/completion"); + + finish_language(); } } } // namespace GDScriptTests