/**************************************************************************/ /* gdscript_editor.cpp */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /**************************************************************************/ /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ #include "gdscript.h" #include "core/config/engine.h" #include "core/core_constants.h" #include "core/io/file_access.h" #include "gdscript_analyzer.h" #include "gdscript_compiler.h" #include "gdscript_parser.h" #include "gdscript_tokenizer.h" #include "gdscript_utility_functions.h" #ifdef TOOLS_ENABLED #include "core/config/project_settings.h" #include "editor/editor_file_system.h" #include "editor/editor_settings.h" #include "editor/script_templates/templates.gen.h" #endif void GDScriptLanguage::get_comment_delimiters(List<String> *p_delimiters) const { p_delimiters->push_back("#"); } void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const { p_delimiters->push_back("\" \""); p_delimiters->push_back("' '"); p_delimiters->push_back("\"\"\" \"\"\""); } bool GDScriptLanguage::is_using_templates() { return true; } Ref<Script> GDScriptLanguage::make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const { Ref<GDScript> scr; scr.instantiate(); String processed_template = p_template; bool type_hints = false; #ifdef TOOLS_ENABLED type_hints = EDITOR_GET("text_editor/completion/add_type_hints"); #endif if (!type_hints) { processed_template = processed_template.replace(": int", "") .replace(": String", "") .replace(": Array[String]", "") .replace(": float", "") .replace(":=", "=") .replace(" -> String", "") .replace(" -> int", "") .replace(" -> void", ""); } processed_template = processed_template.replace("_BASE_", p_base_class_name) .replace("_CLASS_", p_class_name.to_pascal_case()) .replace("_TS_", _get_indentation()); scr->set_source_code(processed_template); return scr; } Vector<ScriptLanguage::ScriptTemplate> GDScriptLanguage::get_built_in_templates(StringName p_object) { Vector<ScriptLanguage::ScriptTemplate> templates; #ifdef TOOLS_ENABLED for (int i = 0; i < TEMPLATES_ARRAY_SIZE; i++) { if (TEMPLATES[i].inherit == p_object) { templates.append(TEMPLATES[i]); } } #endif return templates; } static void get_function_names_recursively(const GDScriptParser::ClassNode *p_class, const String &p_prefix, HashMap<int, String> &r_funcs) { for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type == GDScriptParser::ClassNode::Member::FUNCTION) { const GDScriptParser::FunctionNode *function = p_class->members[i].function; r_funcs[function->start_line] = p_prefix.is_empty() ? String(function->identifier->name) : p_prefix + "." + String(function->identifier->name); } else if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) { String new_prefix = p_class->members[i].m_class->identifier->name; get_function_names_recursively(p_class->members[i].m_class, p_prefix.is_empty() ? new_prefix : p_prefix + "." + new_prefix, r_funcs); } } } bool GDScriptLanguage::validate(const String &p_script, const String &p_path, List<String> *r_functions, List<ScriptLanguage::ScriptError> *r_errors, List<ScriptLanguage::Warning> *r_warnings, HashSet<int> *r_safe_lines) const { GDScriptParser parser; GDScriptAnalyzer analyzer(&parser); Error err = parser.parse(p_script, p_path, false); if (err == OK) { err = analyzer.analyze(); } #ifdef DEBUG_ENABLED if (r_warnings) { for (const GDScriptWarning &E : parser.get_warnings()) { const GDScriptWarning &warn = E; ScriptLanguage::Warning w; w.start_line = warn.start_line; w.end_line = warn.end_line; w.leftmost_column = warn.leftmost_column; w.rightmost_column = warn.rightmost_column; w.code = (int)warn.code; w.string_code = GDScriptWarning::get_name_from_code(warn.code); w.message = warn.get_message(); r_warnings->push_back(w); } } #endif if (err) { if (r_errors) { for (const GDScriptParser::ParserError &E : parser.get_errors()) { const GDScriptParser::ParserError &pe = E; ScriptLanguage::ScriptError e; e.line = pe.line; e.column = pe.column; e.message = pe.message; r_errors->push_back(e); } } return false; } else { const GDScriptParser::ClassNode *cl = parser.get_tree(); HashMap<int, String> funcs; get_function_names_recursively(cl, "", funcs); for (const KeyValue<int, String> &E : funcs) { r_functions->push_back(E.value + ":" + itos(E.key)); } } #ifdef DEBUG_ENABLED if (r_safe_lines) { const HashSet<int> &unsafe_lines = parser.get_unsafe_lines(); for (int i = 1; i <= parser.get_last_line_number(); i++) { if (!unsafe_lines.has(i)) { r_safe_lines->insert(i); } } } #endif return true; } bool GDScriptLanguage::has_named_classes() const { return false; } bool GDScriptLanguage::supports_builtin_mode() const { return true; } bool GDScriptLanguage::supports_documentation() const { return true; } int GDScriptLanguage::find_function(const String &p_function, const String &p_code) const { GDScriptTokenizer tokenizer; tokenizer.set_source_code(p_code); int indent = 0; GDScriptTokenizer::Token current = tokenizer.scan(); while (current.type != GDScriptTokenizer::Token::TK_EOF && current.type != GDScriptTokenizer::Token::ERROR) { if (current.type == GDScriptTokenizer::Token::INDENT) { indent++; } else if (current.type == GDScriptTokenizer::Token::DEDENT) { indent--; } if (indent == 0 && current.type == GDScriptTokenizer::Token::FUNC) { current = tokenizer.scan(); if (current.is_identifier()) { String identifier = current.get_identifier(); if (identifier == p_function) { return current.start_line; } } } current = tokenizer.scan(); } return -1; } Script *GDScriptLanguage::create_script() const { return memnew(GDScript); } /* DEBUGGER FUNCTIONS */ bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) { // break because of parse error if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) { _debug_parse_err_line = p_line; _debug_parse_err_file = p_file; _debug_error = p_error; EngineDebugger::get_script_debugger()->debug(this, false, true); return true; } else { return false; } } bool GDScriptLanguage::debug_break(const String &p_error, bool p_allow_continue) { if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) { _debug_parse_err_line = -1; _debug_parse_err_file = ""; _debug_error = p_error; bool is_error_breakpoint = p_error != "Breakpoint"; EngineDebugger::get_script_debugger()->debug(this, p_allow_continue, is_error_breakpoint); return true; } else { return false; } } String GDScriptLanguage::debug_get_error() const { return _debug_error; } int GDScriptLanguage::debug_get_stack_level_count() const { if (_debug_parse_err_line >= 0) { return 1; } return _debug_call_stack_pos; } int GDScriptLanguage::debug_get_stack_level_line(int p_level) const { if (_debug_parse_err_line >= 0) { return _debug_parse_err_line; } ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, -1); int l = _debug_call_stack_pos - p_level - 1; return *(_call_stack[l].line); } String GDScriptLanguage::debug_get_stack_level_function(int p_level) const { if (_debug_parse_err_line >= 0) { return ""; } ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, ""); int l = _debug_call_stack_pos - p_level - 1; return _call_stack[l].function->get_name(); } String GDScriptLanguage::debug_get_stack_level_source(int p_level) const { if (_debug_parse_err_line >= 0) { return _debug_parse_err_file; } ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, ""); int l = _debug_call_stack_pos - p_level - 1; return _call_stack[l].function->get_source(); } void GDScriptLanguage::debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) { if (_debug_parse_err_line >= 0) { return; } ERR_FAIL_INDEX(p_level, _debug_call_stack_pos); int l = _debug_call_stack_pos - p_level - 1; GDScriptFunction *f = _call_stack[l].function; List<Pair<StringName, int>> locals; f->debug_get_stack_member_state(*_call_stack[l].line, &locals); for (const Pair<StringName, int> &E : locals) { p_locals->push_back(E.first); p_values->push_back(_call_stack[l].stack[E.second]); } } void GDScriptLanguage::debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems, int p_max_depth) { if (_debug_parse_err_line >= 0) { return; } ERR_FAIL_INDEX(p_level, _debug_call_stack_pos); int l = _debug_call_stack_pos - p_level - 1; GDScriptInstance *instance = _call_stack[l].instance; if (!instance) { return; } Ref<GDScript> scr = instance->get_script(); ERR_FAIL_COND(scr.is_null()); const HashMap<StringName, GDScript::MemberInfo> &mi = scr->debug_get_member_indices(); 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)); } } ScriptInstance *GDScriptLanguage::debug_get_stack_level_instance(int p_level) { if (_debug_parse_err_line >= 0) { return nullptr; } ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, nullptr); int l = _debug_call_stack_pos - p_level - 1; ScriptInstance *instance = _call_stack[l].instance; return instance; } void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) { const HashMap<StringName, int> &name_idx = GDScriptLanguage::get_singleton()->get_global_map(); const Variant *gl_array = GDScriptLanguage::get_singleton()->get_global_array(); List<Pair<String, Variant>> cinfo; get_public_constants(&cinfo); 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) { is_script_constant = true; break; } } if (is_script_constant) { continue; } const Variant &var = gl_array[E.value]; if (Object *obj = var) { if (Object::cast_to<GDScriptNativeClass>(obj)) { continue; } } bool skip = false; for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) { if (E.key == CoreConstants::get_global_constant_name(i)) { skip = true; break; } } if (skip) { continue; } p_globals->push_back(E.key); p_values->push_back(var); } } String GDScriptLanguage::debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) { return ""; } void GDScriptLanguage::get_recognized_extensions(List<String> *p_extensions) const { p_extensions->push_back("gd"); } void GDScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const { List<StringName> functions; GDScriptUtilityFunctions::get_function_list(&functions); for (const StringName &E : functions) { p_functions->push_back(GDScriptUtilityFunctions::get_function_info(E)); } // Not really "functions", but show in documentation. { MethodInfo mi; mi.name = "preload"; mi.arguments.push_back(PropertyInfo(Variant::STRING, "path")); mi.return_val = PropertyInfo(Variant::OBJECT, "", PROPERTY_HINT_RESOURCE_TYPE, "Resource"); p_functions->push_back(mi); } { MethodInfo mi; mi.name = "assert"; mi.return_val.type = Variant::NIL; mi.arguments.push_back(PropertyInfo(Variant::BOOL, "condition")); mi.arguments.push_back(PropertyInfo(Variant::STRING, "message")); mi.default_arguments.push_back(String()); p_functions->push_back(mi); } } void GDScriptLanguage::get_public_constants(List<Pair<String, Variant>> *p_constants) const { Pair<String, Variant> pi; pi.first = "PI"; pi.second = Math_PI; p_constants->push_back(pi); Pair<String, Variant> tau; tau.first = "TAU"; tau.second = Math_TAU; p_constants->push_back(tau); Pair<String, Variant> infinity; infinity.first = "INF"; infinity.second = INFINITY; p_constants->push_back(infinity); Pair<String, Variant> nan; nan.first = "NAN"; nan.second = NAN; p_constants->push_back(nan); } void GDScriptLanguage::get_public_annotations(List<MethodInfo> *p_annotations) const { GDScriptParser parser; List<MethodInfo> annotations; parser.get_annotation_list(&annotations); for (const MethodInfo &E : annotations) { p_annotations->push_back(E); } } String GDScriptLanguage::make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const { #ifdef TOOLS_ENABLED bool th = EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints"); #else bool th = false; #endif String s = "func " + p_name + "("; if (p_args.size()) { for (int i = 0; i < p_args.size(); i++) { if (i > 0) { s += ", "; } s += p_args[i].get_slice(":", 0); if (th) { String type = p_args[i].get_slice(":", 1); if (!type.is_empty() && type != "var") { s += ": " + type; } } } } s += String(")") + (th ? " -> void" : "") + ":\n" + _get_indentation() + "pass # Replace with function body.\n"; return s; } //////// COMPLETION ////////// #ifdef TOOLS_ENABLED #define COMPLETION_RECURSION_LIMIT 200 struct GDScriptCompletionIdentifier { GDScriptParser::DataType type; String enumeration; Variant value; const GDScriptParser::ExpressionNode *assigned_expression = nullptr; }; // LOCATION METHODS // These methods are used to populate the `CodeCompletionOption::location` integer. // For these methods, the location is based on the depth in the inheritance chain that the property // appears. For example, if you are completing code in a class that inherits Node2D, a property found on Node2D // will have a "better" (lower) location "score" than a property that is found on CanvasItem. static int _get_property_location(StringName p_class, StringName p_property) { if (!ClassDB::has_property(p_class, p_property)) { return ScriptLanguage::LOCATION_OTHER; } int depth = 0; StringName class_test = p_class; while (class_test && !ClassDB::has_property(class_test, p_property, true)) { class_test = ClassDB::get_parent_class(class_test); depth++; } return depth | ScriptLanguage::LOCATION_PARENT_MASK; } static int _get_constant_location(StringName p_class, StringName p_constant) { if (!ClassDB::has_integer_constant(p_class, p_constant)) { return ScriptLanguage::LOCATION_OTHER; } int depth = 0; StringName class_test = p_class; while (class_test && !ClassDB::has_integer_constant(class_test, p_constant, true)) { class_test = ClassDB::get_parent_class(class_test); depth++; } return depth | ScriptLanguage::LOCATION_PARENT_MASK; } static int _get_signal_location(StringName p_class, StringName p_signal) { if (!ClassDB::has_signal(p_class, p_signal)) { return ScriptLanguage::LOCATION_OTHER; } int depth = 0; StringName class_test = p_class; while (class_test && !ClassDB::has_signal(class_test, p_signal, true)) { class_test = ClassDB::get_parent_class(class_test); depth++; } return depth | ScriptLanguage::LOCATION_PARENT_MASK; } static int _get_method_location(StringName p_class, StringName p_method) { if (!ClassDB::has_method(p_class, p_method)) { return ScriptLanguage::LOCATION_OTHER; } int depth = 0; StringName class_test = p_class; while (class_test && !ClassDB::has_method(class_test, p_method, true)) { class_test = ClassDB::get_parent_class(class_test); depth++; } return depth | ScriptLanguage::LOCATION_PARENT_MASK; } static int _get_enum_constant_location(StringName p_class, StringName p_enum_constant) { if (!ClassDB::get_integer_constant_enum(p_class, p_enum_constant)) { return ScriptLanguage::LOCATION_OTHER; } int depth = 0; StringName class_test = p_class; while (class_test && !ClassDB::get_integer_constant_enum(class_test, p_enum_constant, true)) { class_test = ClassDB::get_parent_class(class_test); depth++; } return depth | ScriptLanguage::LOCATION_PARENT_MASK; } // END LOCATION METHODS static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg = true) { if (p_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) { String enum_name = p_info.class_name; if (!enum_name.contains(".")) { return enum_name; } return enum_name.get_slice(".", 1); } String n = p_info.name; int idx = n.find(":"); if (idx != -1) { return n.substr(idx + 1, n.length()); } if (p_info.type == Variant::OBJECT) { if (p_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { return p_info.hint_string; } else { return p_info.class_name.operator String(); } } if (p_info.type == Variant::NIL) { if (p_is_arg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) { return "Variant"; } else { return "void"; } } return Variant::get_type_name(p_info.type); } static String _make_arguments_hint(const MethodInfo &p_info, int p_arg_idx, bool p_is_annotation = false) { String arghint; if (!p_is_annotation) { arghint += _get_visual_datatype(p_info.return_val, false) + " "; } arghint += p_info.name + "("; int def_args = p_info.arguments.size() - p_info.default_arguments.size(); int i = 0; for (const PropertyInfo &E : p_info.arguments) { if (i > 0) { arghint += ", "; } if (i == p_arg_idx) { arghint += String::chr(0xFFFF); } arghint += E.name + ": " + _get_visual_datatype(E, true); if (i - def_args >= 0) { arghint += String(" = ") + p_info.default_arguments[i - def_args].get_construct_string(); } if (i == p_arg_idx) { arghint += String::chr(0xFFFF); } i++; } if (p_info.flags & METHOD_FLAG_VARARG) { if (p_info.arguments.size() > 0) { arghint += ", "; } if (p_arg_idx >= p_info.arguments.size()) { arghint += String::chr(0xFFFF); } arghint += "..."; if (p_arg_idx >= p_info.arguments.size()) { arghint += String::chr(0xFFFF); } } arghint += ")"; return arghint; } static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_function, int p_arg_idx) { String arghint; if (p_function->get_datatype().builtin_type == Variant::NIL) { arghint = "void " + p_function->identifier->name.operator String() + "("; } else { arghint = p_function->get_datatype().to_string() + " " + p_function->identifier->name.operator String() + "("; } for (int i = 0; i < p_function->parameters.size(); i++) { if (i > 0) { arghint += ", "; } if (i == p_arg_idx) { arghint += String::chr(0xFFFF); } const GDScriptParser::ParameterNode *par = p_function->parameters[i]; if (!par->get_datatype().is_hard_type()) { arghint += par->identifier->name.operator String() + ": Variant"; } else { arghint += par->identifier->name.operator String() + ": " + par->get_datatype().to_string(); } if (par->initializer) { String def_val = "<unknown>"; switch (par->initializer->type) { case GDScriptParser::Node::LITERAL: { const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(par->initializer); def_val = literal->value.get_construct_string(); } break; case GDScriptParser::Node::IDENTIFIER: { const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(par->initializer); def_val = id->name.operator String(); } break; case GDScriptParser::Node::CALL: { const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(par->initializer); if (call->is_constant && call->reduced) { def_val = call->function_name.operator String() + call->reduced_value.operator String(); } } break; case GDScriptParser::Node::ARRAY: { const GDScriptParser::ArrayNode *arr = static_cast<const GDScriptParser::ArrayNode *>(par->initializer); if (arr->is_constant && arr->reduced) { def_val = arr->reduced_value.operator String(); } } break; case GDScriptParser::Node::DICTIONARY: { const GDScriptParser::DictionaryNode *dict = static_cast<const GDScriptParser::DictionaryNode *>(par->initializer); if (dict->is_constant && dict->reduced) { def_val = dict->reduced_value.operator String(); } } break; case GDScriptParser::Node::SUBSCRIPT: { const GDScriptParser::SubscriptNode *sub = static_cast<const GDScriptParser::SubscriptNode *>(par->initializer); if (sub->is_constant) { if (sub->datatype.kind == GDScriptParser::DataType::ENUM) { def_val = sub->get_datatype().to_string(); } else if (sub->reduced) { const Variant::Type vt = sub->reduced_value.get_type(); if (vt == Variant::Type::NIL || vt == Variant::Type::FLOAT || vt == Variant::Type::INT || vt == Variant::Type::STRING || vt == Variant::Type::STRING_NAME || vt == Variant::Type::BOOL || vt == Variant::Type::NODE_PATH) { def_val = sub->reduced_value.operator String(); } else { def_val = sub->get_datatype().to_string() + sub->reduced_value.operator String(); } } } } break; default: break; } arghint += " = " + def_val; } if (i == p_arg_idx) { arghint += String::chr(0xFFFF); } } arghint += ")"; return arghint; } static void _get_directory_contents(EditorFileSystemDirectory *p_dir, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_list) { const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; for (int i = 0; i < p_dir->get_file_count(); i++) { ScriptLanguage::CodeCompletionOption option(p_dir->get_file_path(i), ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH); option.insert_text = option.display.quote(quote_style); r_list.insert(option.display, option); } for (int i = 0; i < p_dir->get_subdir_count(); i++) { _get_directory_contents(p_dir->get_subdir(i), r_list); } } static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_annotation, int p_argument, const String p_quote_style, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result) { if (p_annotation->name == SNAME("@export_range")) { if (p_argument == 3 || p_argument == 4 || p_argument == 5) { // Slider hint. ScriptLanguage::CodeCompletionOption slider1("or_greater", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); slider1.insert_text = slider1.display.quote(p_quote_style); r_result.insert(slider1.display, slider1); ScriptLanguage::CodeCompletionOption slider2("or_less", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); slider2.insert_text = slider2.display.quote(p_quote_style); r_result.insert(slider2.display, slider2); ScriptLanguage::CodeCompletionOption slider3("hide_slider", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); slider3.insert_text = slider3.display.quote(p_quote_style); r_result.insert(slider3.display, slider3); } } else if (p_annotation->name == SNAME("@export_exp_easing")) { if (p_argument == 0 || p_argument == 1) { // Easing hint. ScriptLanguage::CodeCompletionOption hint1("attenuation", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); hint1.insert_text = hint1.display.quote(p_quote_style); r_result.insert(hint1.display, hint1); ScriptLanguage::CodeCompletionOption hint2("inout", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); hint2.insert_text = hint2.display.quote(p_quote_style); r_result.insert(hint2.display, hint2); } } else if (p_annotation->name == SNAME("@export_node_path")) { ScriptLanguage::CodeCompletionOption node("Node", ScriptLanguage::CODE_COMPLETION_KIND_CLASS); node.insert_text = node.display.quote(p_quote_style); r_result.insert(node.display, node); List<StringName> node_types; ClassDB::get_inheriters_from_class("Node", &node_types); for (const StringName &E : node_types) { if (!ClassDB::is_class_exposed(E)) { continue; } ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS); option.insert_text = option.display.quote(p_quote_style); r_result.insert(option.display, option); } } else if (p_annotation->name == SNAME("@warning_ignore")) { for (int warning_code = 0; warning_code < GDScriptWarning::WARNING_MAX; warning_code++) { ScriptLanguage::CodeCompletionOption warning(GDScriptWarning::get_name_from_code((GDScriptWarning::Code)warning_code).to_lower(), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); warning.insert_text = warning.display.quote(p_quote_style); r_result.insert(warning.display, warning); } } else if (p_annotation->name == SNAME("@rpc")) { if (p_argument == 0 || p_argument == 1 || p_argument == 2) { static const char *options[7] = { "call_local", "call_remote", "any_peer", "authority", "reliable", "unreliable", "unreliable_ordered" }; for (int i = 0; i < 7; i++) { ScriptLanguage::CodeCompletionOption option(options[i], ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); option.insert_text = option.display.quote(p_quote_style); r_result.insert(option.display, option); } } } } static void _find_built_in_variants(HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, bool exclude_nil = false) { for (int i = 0; i < Variant::VARIANT_MAX; i++) { if (!exclude_nil && Variant::Type(i) == Variant::Type::NIL) { ScriptLanguage::CodeCompletionOption option("null", ScriptLanguage::CODE_COMPLETION_KIND_CLASS); r_result.insert(option.display, option); } else { ScriptLanguage::CodeCompletionOption option(Variant::get_type_name(Variant::Type(i)), ScriptLanguage::CODE_COMPLETION_KIND_CLASS); r_result.insert(option.display, option); } } } static void _list_available_types(bool p_inherit_only, GDScriptParser::CompletionContext &p_context, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result) { // Built-in Variant Types _find_built_in_variants(r_result, true); List<StringName> native_types; ClassDB::get_class_list(&native_types); for (const StringName &E : native_types) { if (ClassDB::is_class_exposed(E) && !Engine::get_singleton()->has_singleton(E)) { ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS); r_result.insert(option.display, option); } } if (p_context.current_class) { if (!p_inherit_only && p_context.current_class->base_type.is_set()) { // Native enums from base class List<StringName> enums; ClassDB::get_enum_list(p_context.current_class->base_type.native_type, &enums); for (const StringName &E : enums) { ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_ENUM); r_result.insert(option.display, option); } } // Check current class for potential types const GDScriptParser::ClassNode *current = p_context.current_class; 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); 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); 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); r_result.insert(option.display, option); } } break; default: break; } } current = current->outer; } } // Global scripts List<StringName> global_classes; ScriptServer::get_global_class_list(&global_classes); for (const StringName &E : global_classes) { ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_OTHER_USER_CODE); r_result.insert(option.display, option); } // Autoload singletons HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : autoloads) { const ProjectSettings::AutoloadInfo &info = E.value; if (!info.is_singleton || info.path.get_extension().to_lower() != "gd") { continue; } ScriptLanguage::CodeCompletionOption option(info.name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_OTHER_USER_CODE); r_result.insert(option.display, option); } } static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result) { for (int i = 0; i < p_suite->locals.size(); i++) { ScriptLanguage::CodeCompletionOption option; if (p_suite->locals[i].type == GDScriptParser::SuiteNode::Local::CONSTANT) { option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, ScriptLanguage::LOCATION_LOCAL); option.default_value = p_suite->locals[i].constant->initializer->reduced_value; } else { option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE, ScriptLanguage::LOCATION_LOCAL); } r_result.insert(option.display, option); } if (p_suite->parent_block) { _find_identifiers_in_suite(p_suite->parent_block, r_result); } } static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth); static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, bool p_only_functions, bool p_static, bool p_parent_only, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth) { ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT); if (!p_parent_only) { bool outer = false; const GDScriptParser::ClassNode *clss = p_class; int classes_processed = 0; while (clss) { for (int i = 0; i < clss->members.size(); i++) { const int location = (classes_processed + p_recursion_depth) | ScriptLanguage::LOCATION_PARENT_MASK; const GDScriptParser::ClassNode::Member &member = clss->members[i]; ScriptLanguage::CodeCompletionOption option; switch (member.type) { case GDScriptParser::ClassNode::Member::VARIABLE: if (p_only_functions || outer || (p_static)) { continue; } option = ScriptLanguage::CodeCompletionOption(member.variable->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); break; case GDScriptParser::ClassNode::Member::CONSTANT: if (p_only_functions) { continue; } if (r_result.has(member.constant->identifier->name)) { continue; } option = ScriptLanguage::CodeCompletionOption(member.constant->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); if (member.constant->initializer) { option.default_value = member.constant->initializer->reduced_value; } break; case GDScriptParser::ClassNode::Member::CLASS: if (p_only_functions) { continue; } option = ScriptLanguage::CodeCompletionOption(member.m_class->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, location); break; case GDScriptParser::ClassNode::Member::ENUM_VALUE: if (p_only_functions) { continue; } option = ScriptLanguage::CodeCompletionOption(member.enum_value.identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); break; case GDScriptParser::ClassNode::Member::ENUM: if (p_only_functions) { continue; } option = ScriptLanguage::CodeCompletionOption(member.m_enum->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_ENUM, location); break; case GDScriptParser::ClassNode::Member::FUNCTION: if (outer || (p_static && !member.function->is_static) || member.function->identifier->name.operator String().begins_with("@")) { continue; } option = ScriptLanguage::CodeCompletionOption(member.function->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location); if (member.function->parameters.size() > 0) { option.insert_text += "("; } else { option.insert_text += "()"; } break; case GDScriptParser::ClassNode::Member::SIGNAL: if (p_only_functions || outer || p_static) { continue; } option = ScriptLanguage::CodeCompletionOption(member.signal->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); break; case GDScriptParser::ClassNode::Member::GROUP: break; // No-op, but silences warnings. case GDScriptParser::ClassNode::Member::UNDEFINED: break; } r_result.insert(option.display, option); } outer = true; clss = clss->outer; classes_processed++; } } // Parents. GDScriptCompletionIdentifier base_type; base_type.type = p_class->base_type; base_type.type.is_meta_type = p_static; _find_identifiers_in_base(base_type, p_only_functions, r_result, p_recursion_depth + 1); } static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth) { ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT); GDScriptParser::DataType base_type = p_base.type; if (base_type.is_meta_type && base_type.kind != GDScriptParser::DataType::BUILTIN && base_type.kind != GDScriptParser::DataType::ENUM) { ScriptLanguage::CodeCompletionOption option("new", ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, ScriptLanguage::LOCATION_LOCAL); option.insert_text += "("; r_result.insert(option.display, option); } while (!base_type.has_no_type()) { switch (base_type.kind) { case GDScriptParser::DataType::CLASS: { _find_identifiers_in_class(base_type.class_type, p_only_functions, base_type.is_meta_type, false, r_result, p_recursion_depth + 1); // This already finds all parent identifiers, so we are done. base_type = GDScriptParser::DataType(); } break; case GDScriptParser::DataType::SCRIPT: { Ref<Script> scr = base_type.script_type; if (scr.is_valid()) { if (!p_only_functions) { if (!base_type.is_meta_type) { List<PropertyInfo> members; scr->get_script_property_list(&members); for (const PropertyInfo &E : members) { int location = p_recursion_depth + _get_property_location(scr->get_class_name(), E.class_name); ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); r_result.insert(option.display, option); } List<MethodInfo> signals; scr->get_script_signal_list(&signals); for (const MethodInfo &E : signals) { int location = p_recursion_depth + _get_signal_location(scr->get_class_name(), E.name); ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); r_result.insert(option.display, option); } } HashMap<StringName, Variant> constants; scr->get_constants(&constants); for (const KeyValue<StringName, Variant> &E : constants) { int location = p_recursion_depth + _get_constant_location(scr->get_class_name(), E.key); ScriptLanguage::CodeCompletionOption option(E.key.operator String(), ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); r_result.insert(option.display, option); } } List<MethodInfo> methods; scr->get_script_method_list(&methods); for (const MethodInfo &E : methods) { if (E.name.begins_with("@")) { continue; } int location = p_recursion_depth + _get_method_location(scr->get_class_name(), E.name); ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location); if (E.arguments.size()) { option.insert_text += "("; } else { option.insert_text += "()"; } r_result.insert(option.display, option); } Ref<Script> base_script = scr->get_base_script(); if (base_script.is_valid()) { base_type.script_type = base_script; } else { base_type.kind = GDScriptParser::DataType::NATIVE; base_type.native_type = scr->get_instance_base_type(); } } else { return; } } break; case GDScriptParser::DataType::NATIVE: { StringName type = base_type.native_type; if (!ClassDB::class_exists(type)) { return; } if (!p_only_functions) { List<String> constants; ClassDB::get_integer_constant_list(type, &constants); for (const String &E : constants) { int location = p_recursion_depth + _get_constant_location(type, StringName(E)); ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); r_result.insert(option.display, option); } if (!base_type.is_meta_type || Engine::get_singleton()->has_singleton(type)) { List<PropertyInfo> pinfo; ClassDB::get_property_list(type, &pinfo); for (const PropertyInfo &E : pinfo) { if (E.usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) { continue; } if (E.name.contains("/")) { continue; } int location = p_recursion_depth + _get_property_location(type, E.class_name); ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); r_result.insert(option.display, option); } List<MethodInfo> signals; ClassDB::get_signal_list(type, &signals); for (const MethodInfo &E : signals) { int location = p_recursion_depth + _get_signal_location(type, StringName(E.name)); ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); r_result.insert(option.display, option); } } } bool only_static = base_type.is_meta_type && !Engine::get_singleton()->has_singleton(type); List<MethodInfo> methods; ClassDB::get_method_list(type, &methods, false, true); for (const MethodInfo &E : methods) { if (only_static && (E.flags & METHOD_FLAG_STATIC) == 0) { continue; } if (E.name.begins_with("_")) { continue; } int location = p_recursion_depth + _get_method_location(type, E.name); ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location); if (E.arguments.size()) { option.insert_text += "("; } else { option.insert_text += "()"; } r_result.insert(option.display, option); } return; } break; case GDScriptParser::DataType::ENUM: case GDScriptParser::DataType::BUILTIN: { Callable::CallError err; Variant tmp; Variant::construct(base_type.builtin_type, tmp, nullptr, 0, err); if (err.error != Callable::CallError::CALL_OK) { return; } if (!p_only_functions) { List<PropertyInfo> members; if (p_base.value.get_type() != Variant::NIL) { p_base.value.get_property_list(&members); } else { tmp.get_property_list(&members); } for (const PropertyInfo &E : members) { if (!String(E.name).contains("/")) { ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER); r_result.insert(option.display, option); } } } List<MethodInfo> methods; tmp.get_method_list(&methods); for (const MethodInfo &E : methods) { if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type && !(E.flags & METHOD_FLAG_CONST)) { // Enum types are static and cannot change, therefore we skip non-const dictionary methods. continue; } ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); if (E.arguments.size()) { option.insert_text += "("; } else { option.insert_text += "()"; } r_result.insert(option.display, option); } return; } break; default: { return; } break; } } } static void _find_identifiers(const GDScriptParser::CompletionContext &p_context, bool p_only_functions, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth) { if (!p_only_functions && p_context.current_suite) { // This includes function parameters, since they are also locals. _find_identifiers_in_suite(p_context.current_suite, r_result); } if (p_context.current_class) { _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth + 1); } List<StringName> functions; GDScriptUtilityFunctions::get_function_list(&functions); for (const StringName &E : functions) { MethodInfo function = GDScriptUtilityFunctions::get_function_info(E); ScriptLanguage::CodeCompletionOption option(String(E), ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); if (function.arguments.size() || (function.flags & METHOD_FLAG_VARARG)) { option.insert_text += "("; } else { option.insert_text += "()"; } r_result.insert(option.display, option); } if (p_only_functions) { return; } _find_built_in_variants(r_result); static const char *_keywords[] = { "true", "false", "PI", "TAU", "INF", "NAN", "null", "self", "super", "break", "breakpoint", "continue", "pass", "return", nullptr }; const char **kw = _keywords; while (*kw) { ScriptLanguage::CodeCompletionOption option(*kw, ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); r_result.insert(option.display, option); kw++; } static const char *_keywords_with_space[] = { "and", "not", "or", "in", "as", "class", "class_name", "extends", "is", "func", "signal", "await", "const", "enum", "static", "var", "if", "elif", "else", "for", "match", "while", nullptr }; const char **kws = _keywords_with_space; while (*kws) { ScriptLanguage::CodeCompletionOption option(*kws, ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); option.insert_text += " "; r_result.insert(option.display, option); kws++; } static const char *_keywords_with_args[] = { "assert", "preload", nullptr }; const char **kwa = _keywords_with_args; while (*kwa) { ScriptLanguage::CodeCompletionOption option(*kwa, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); option.insert_text += "("; r_result.insert(option.display, option); kwa++; } List<StringName> utility_func_names; Variant::get_utility_function_list(&utility_func_names); for (List<StringName>::Element *E = utility_func_names.front(); E; E = E->next()) { ScriptLanguage::CodeCompletionOption option(E->get(), ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); option.insert_text += "("; r_result.insert(option.display, option); } for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) { if (!E.value.is_singleton) { continue; } ScriptLanguage::CodeCompletionOption option(E.key, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT); r_result.insert(option.display, option); } // Native classes and global constants. for (const KeyValue<StringName, int> &E : GDScriptLanguage::get_singleton()->get_global_map()) { ScriptLanguage::CodeCompletionOption option; if (ClassDB::class_exists(E.key) || Engine::get_singleton()->has_singleton(E.key)) { option = ScriptLanguage::CodeCompletionOption(E.key.operator String(), ScriptLanguage::CODE_COMPLETION_KIND_CLASS); } else { option = ScriptLanguage::CodeCompletionOption(E.key.operator String(), ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT); } r_result.insert(option.display, option); } // Global classes List<StringName> global_classes; ScriptServer::get_global_class_list(&global_classes); for (const StringName &E : global_classes) { ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_OTHER_USER_CODE); r_result.insert(option.display, option); } } static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value) { GDScriptCompletionIdentifier ci; ci.value = p_value; ci.type.is_constant = true; ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; ci.type.kind = GDScriptParser::DataType::BUILTIN; ci.type.builtin_type = p_value.get_type(); if (ci.type.builtin_type == Variant::OBJECT) { Object *obj = p_value.operator Object *(); if (!obj) { return ci; } ci.type.native_type = obj->get_class_name(); Ref<Script> scr = p_value; if (scr.is_valid()) { ci.type.is_meta_type = true; } else { ci.type.is_meta_type = false; scr = obj->get_script(); } if (scr.is_valid()) { ci.type.script_type = scr; ci.type.kind = GDScriptParser::DataType::SCRIPT; ci.type.native_type = scr->get_instance_base_type(); } else { ci.type.kind = GDScriptParser::DataType::NATIVE; } } return ci; } static GDScriptCompletionIdentifier _type_from_property(const PropertyInfo &p_property) { GDScriptCompletionIdentifier ci; if (p_property.type == Variant::NIL) { // Variant return ci; } if (p_property.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) { ci.enumeration = p_property.class_name; } ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; ci.type.builtin_type = p_property.type; if (p_property.type == Variant::OBJECT) { ci.type.kind = GDScriptParser::DataType::NATIVE; ci.type.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; } else { ci.type.kind = GDScriptParser::DataType::BUILTIN; } return ci; } static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type); static bool _is_expression_named_identifier(const GDScriptParser::ExpressionNode *p_expression, const StringName &p_name) { if (p_expression) { switch (p_expression->type) { case GDScriptParser::Node::IDENTIFIER: { const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(p_expression); if (id->name == p_name) { return true; } } break; case GDScriptParser::Node::CAST: { const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); return _is_expression_named_identifier(cn->operand, p_name); } break; default: break; } } return false; } static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::ExpressionNode *p_expression, GDScriptCompletionIdentifier &r_type) { bool found = false; if (p_expression == nullptr) { return false; } if (p_expression->is_constant) { // Already has a value, so just use that. r_type = _type_from_variant(p_expression->reduced_value); if (p_expression->get_datatype().kind == GDScriptParser::DataType::ENUM) { r_type.type = p_expression->get_datatype(); } found = true; } else { switch (p_expression->type) { case GDScriptParser::Node::LITERAL: { const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(p_expression); r_type = _type_from_variant(literal->value); found = true; } break; case GDScriptParser::Node::SELF: { if (p_context.current_class) { r_type.type.kind = GDScriptParser::DataType::CLASS; r_type.type.type_source = GDScriptParser::DataType::INFERRED; r_type.type.is_constant = true; r_type.type.class_type = p_context.current_class; r_type.value = p_context.base; found = true; } } break; case GDScriptParser::Node::IDENTIFIER: { const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(p_expression); found = _guess_identifier_type(p_context, id->name, r_type); } break; case GDScriptParser::Node::DICTIONARY: { // Try to recreate the dictionary. const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(p_expression); Dictionary d; bool full = true; for (int i = 0; i < dn->elements.size(); i++) { GDScriptCompletionIdentifier key; if (_guess_expression_type(p_context, dn->elements[i].key, key)) { if (!key.type.is_constant) { full = false; break; } GDScriptCompletionIdentifier value; if (_guess_expression_type(p_context, dn->elements[i].value, value)) { if (!value.type.is_constant) { full = false; break; } d[key.value] = value.value; } else { full = false; break; } } else { full = false; break; } } if (full) { r_type.value = d; r_type.type.is_constant = true; } r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.kind = GDScriptParser::DataType::BUILTIN; r_type.type.builtin_type = Variant::DICTIONARY; found = true; } break; case GDScriptParser::Node::ARRAY: { // Try to recreate the array const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_expression); Array a; bool full = true; a.resize(an->elements.size()); for (int i = 0; i < an->elements.size(); i++) { GDScriptCompletionIdentifier value; if (_guess_expression_type(p_context, an->elements[i], value)) { if (value.type.is_constant) { a[i] = value.value; } else { full = false; break; } } else { full = false; break; } } if (full) { // If not fully constant, setting this value is detrimental to the inference. r_type.value = a; } r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.kind = GDScriptParser::DataType::BUILTIN; r_type.type.builtin_type = Variant::ARRAY; found = true; } break; case GDScriptParser::Node::CAST: { const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); GDScriptCompletionIdentifier value; if (_guess_expression_type(p_context, cn->operand, r_type)) { r_type.type = cn->get_datatype(); found = true; } } break; case GDScriptParser::Node::CALL: { const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_expression); if (GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) { r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.kind = GDScriptParser::DataType::BUILTIN; r_type.type.builtin_type = GDScriptParser::get_builtin_type(call->function_name); found = true; break; } else if (GDScriptUtilityFunctions::function_exists(call->function_name)) { MethodInfo mi = GDScriptUtilityFunctions::get_function_info(call->function_name); r_type = _type_from_property(mi.return_val); found = true; break; } else { GDScriptParser::CompletionContext c = p_context; c.current_line = call->start_line; GDScriptParser::Node::Type callee_type = call->get_callee_type(); GDScriptCompletionIdentifier base; if (callee_type == GDScriptParser::Node::IDENTIFIER || call->is_super) { // Simple call, so base is 'self'. if (p_context.current_class) { if (call->is_super) { base.type = p_context.current_class->base_type; base.value = p_context.base; } else { base.type.kind = GDScriptParser::DataType::CLASS; base.type.type_source = GDScriptParser::DataType::INFERRED; base.type.is_constant = true; base.type.class_type = p_context.current_class; base.value = p_context.base; } } else { break; } } else if (callee_type == GDScriptParser::Node::SUBSCRIPT && static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->is_attribute) { if (!_guess_expression_type(c, static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->base, base)) { found = false; break; } } else { break; } // Try call if constant methods with constant arguments if (base.type.is_constant && base.value.get_type() == Variant::OBJECT) { GDScriptParser::DataType native_type = base.type; while (native_type.kind == GDScriptParser::DataType::CLASS) { native_type = native_type.class_type->base_type; } while (native_type.kind == GDScriptParser::DataType::SCRIPT) { if (native_type.script_type.is_valid()) { Ref<Script> parent = native_type.script_type->get_base_script(); if (parent.is_valid()) { native_type.script_type = parent; } else { 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.kind = GDScriptParser::DataType::UNRESOLVED; } } } } if (native_type.kind == GDScriptParser::DataType::NATIVE) { MethodBind *mb = ClassDB::get_method(native_type.native_type, call->function_name); if (mb && mb->is_const()) { bool all_is_const = true; Vector<Variant> args; GDScriptParser::CompletionContext c2 = p_context; c2.current_line = call->start_line; for (int i = 0; all_is_const && i < call->arguments.size(); i++) { GDScriptCompletionIdentifier arg; if (!call->arguments[i]->is_constant) { all_is_const = false; } } Object *baseptr = base.value; if (all_is_const && call->function_name == SNAME("get_node") && ClassDB::is_parent_class(native_type.native_type, SNAME("Node")) && args.size()) { String arg1 = args[0]; if (arg1.begins_with("/root/")) { String which = arg1.get_slice("/", 2); if (!which.is_empty()) { // Try singletons first if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(which)) { r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which]); found = true; } else { for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) { String name = E.key; if (name == which) { String script = E.value.path; if (!script.begins_with("res://")) { script = "res://" + script; } if (!script.ends_with(".gd")) { // not a script, try find the script anyway, // may have some success script = script.get_basename() + ".gd"; } if (FileAccess::exists(script)) { Error err = OK; Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(script, GDScriptParserRef::INTERFACE_SOLVED, err); if (err == OK) { r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.script_path = script; r_type.type.class_type = parser->get_parser()->get_tree(); r_type.type.is_constant = false; r_type.type.kind = GDScriptParser::DataType::CLASS; r_type.value = Variant(); p_context.dependent_parsers.push_back(parser); found = true; } } break; } } } } } } if (!found && all_is_const && baseptr) { Vector<const Variant *> argptr; for (int i = 0; i < args.size(); i++) { argptr.push_back(&args[i]); } Callable::CallError ce; Variant ret = mb->call(baseptr, (const Variant **)argptr.ptr(), argptr.size(), ce); if (ce.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) { if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != nullptr) { r_type = _type_from_variant(ret); found = true; } } } } } } if (!found && base.value.get_type() != Variant::NIL) { found = _guess_method_return_type_from_base(c, base, call->function_name, r_type); } } } break; case GDScriptParser::Node::SUBSCRIPT: { const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_expression); if (subscript->is_attribute) { GDScriptParser::CompletionContext c = p_context; c.current_line = subscript->start_line; GDScriptCompletionIdentifier base; if (!_guess_expression_type(c, subscript->base, base)) { found = false; break; } if (base.value.get_type() == Variant::DICTIONARY && base.value.operator Dictionary().has(String(subscript->attribute->name))) { Variant value = base.value.operator Dictionary()[String(subscript->attribute->name)]; r_type = _type_from_variant(value); found = true; break; } const GDScriptParser::DictionaryNode *dn = nullptr; if (subscript->base->type == GDScriptParser::Node::DICTIONARY) { dn = static_cast<const GDScriptParser::DictionaryNode *>(subscript->base); } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::DICTIONARY) { dn = static_cast<const GDScriptParser::DictionaryNode *>(base.assigned_expression); } if (dn) { for (int i = 0; i < dn->elements.size(); i++) { GDScriptCompletionIdentifier key; if (!_guess_expression_type(c, dn->elements[i].key, key)) { continue; } if (key.value == String(subscript->attribute->name)) { r_type.assigned_expression = dn->elements[i].value; found = _guess_expression_type(c, dn->elements[i].value, r_type); break; } } } if (!found) { found = _guess_identifier_type_from_base(c, base, subscript->attribute->name, r_type); } } else { if (subscript->index == nullptr) { found = false; break; } GDScriptParser::CompletionContext c = p_context; c.current_line = subscript->start_line; GDScriptCompletionIdentifier base; if (!_guess_expression_type(c, subscript->base, base)) { found = false; break; } GDScriptCompletionIdentifier index; if (!_guess_expression_type(c, subscript->index, index)) { found = false; break; } if (base.value.in(index.value)) { Variant value = base.value.get(index.value); r_type = _type_from_variant(value); found = true; break; } // Look if it is a dictionary node. const GDScriptParser::DictionaryNode *dn = nullptr; if (subscript->base->type == GDScriptParser::Node::DICTIONARY) { dn = static_cast<const GDScriptParser::DictionaryNode *>(subscript->base); } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::DICTIONARY) { dn = static_cast<const GDScriptParser::DictionaryNode *>(base.assigned_expression); } if (dn) { for (int i = 0; i < dn->elements.size(); i++) { GDScriptCompletionIdentifier key; if (!_guess_expression_type(c, dn->elements[i].key, key)) { continue; } if (key.value == index.value) { r_type.assigned_expression = dn->elements[i].value; found = _guess_expression_type(p_context, dn->elements[i].value, r_type); break; } } } // Look if it is an array node. if (!found && index.value.is_num()) { int idx = index.value; const GDScriptParser::ArrayNode *an = nullptr; if (subscript->base->type == GDScriptParser::Node::ARRAY) { an = static_cast<const GDScriptParser::ArrayNode *>(subscript->base); } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::ARRAY) { an = static_cast<const GDScriptParser::ArrayNode *>(base.assigned_expression); } if (an && idx >= 0 && an->elements.size() > idx) { r_type.assigned_expression = an->elements[idx]; found = _guess_expression_type(c, an->elements[idx], r_type); break; } } // Look for valid indexing in other types if (!found && (index.value.get_type() == Variant::STRING || index.value.get_type() == Variant::NODE_PATH)) { StringName id = index.value; found = _guess_identifier_type_from_base(c, base, id, r_type); } else if (!found && index.type.kind == GDScriptParser::DataType::BUILTIN) { Callable::CallError err; Variant base_val; Variant::construct(base.type.builtin_type, base_val, nullptr, 0, err); bool valid = false; Variant res = base_val.get(index.value, &valid); if (valid) { r_type = _type_from_variant(res); r_type.value = Variant(); r_type.type.is_constant = false; found = true; } } } } break; case GDScriptParser::Node::BINARY_OPERATOR: { const GDScriptParser::BinaryOpNode *op = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression); if (op->variant_op == Variant::OP_MAX) { break; } GDScriptParser::CompletionContext context = p_context; context.current_line = op->start_line; GDScriptCompletionIdentifier p1; GDScriptCompletionIdentifier p2; if (!_guess_expression_type(context, op->left_operand, p1)) { found = false; break; } if (!_guess_expression_type(context, op->right_operand, p2)) { found = false; break; } Callable::CallError ce; bool v1_use_value = p1.value.get_type() != Variant::NIL && p1.value.get_type() != Variant::OBJECT; Variant d1; Variant::construct(p1.type.builtin_type, d1, nullptr, 0, ce); Variant d2; Variant::construct(p2.type.builtin_type, d2, nullptr, 0, ce); Variant v1 = (v1_use_value) ? p1.value : d1; bool v2_use_value = p2.value.get_type() != Variant::NIL && p2.value.get_type() != Variant::OBJECT; Variant v2 = (v2_use_value) ? p2.value : d2; // avoid potential invalid ops if ((op->variant_op == Variant::OP_DIVIDE || op->variant_op == Variant::OP_MODULE) && v2.get_type() == Variant::INT) { v2 = 1; v2_use_value = false; } if (op->variant_op == Variant::OP_DIVIDE && v2.get_type() == Variant::FLOAT) { v2 = 1.0; v2_use_value = false; } Variant res; bool valid; Variant::evaluate(op->variant_op, v1, v2, res, valid); if (!valid) { found = false; break; } r_type = _type_from_variant(res); if (!v1_use_value || !v2_use_value) { r_type.value = Variant(); r_type.type.is_constant = false; } found = true; } break; default: break; } } // It may have found a null, but that's never useful if (found && r_type.type.kind == GDScriptParser::DataType::BUILTIN && r_type.type.builtin_type == Variant::NIL) { found = false; } // Check type hint last. For collections we want chance to get the actual value first // This way we can detect types from the content of dictionaries and arrays if (!found && p_expression->get_datatype().is_hard_type()) { r_type.type = p_expression->get_datatype(); if (!r_type.assigned_expression) { r_type.assigned_expression = p_expression; } found = true; } return found; } static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) { // Look in blocks first. int last_assign_line = -1; const GDScriptParser::ExpressionNode *last_assigned_expression = nullptr; GDScriptParser::DataType id_type; GDScriptParser::SuiteNode *suite = p_context.current_suite; bool is_function_parameter = false; if (suite) { if (suite->has_local(p_identifier)) { const GDScriptParser::SuiteNode::Local &local = suite->get_local(p_identifier); id_type = local.get_datatype(); // Check initializer as the first assignment. switch (local.type) { case GDScriptParser::SuiteNode::Local::VARIABLE: if (local.variable->initializer) { last_assign_line = local.variable->initializer->end_line; last_assigned_expression = local.variable->initializer; } break; case GDScriptParser::SuiteNode::Local::CONSTANT: if (local.constant->initializer) { last_assign_line = local.constant->initializer->end_line; last_assigned_expression = local.constant->initializer; } break; case GDScriptParser::SuiteNode::Local::PARAMETER: if (local.parameter->initializer) { last_assign_line = local.parameter->initializer->end_line; last_assigned_expression = local.parameter->initializer; } is_function_parameter = true; break; default: break; } } } while (suite) { for (int i = 0; i < suite->statements.size(); i++) { if (suite->statements[i]->end_line >= p_context.current_line) { break; } switch (suite->statements[i]->type) { case GDScriptParser::Node::ASSIGNMENT: { const GDScriptParser::AssignmentNode *assign = static_cast<const GDScriptParser::AssignmentNode *>(suite->statements[i]); if (assign->end_line > last_assign_line && assign->assignee && assign->assigned_value && assign->assignee->type == GDScriptParser::Node::IDENTIFIER) { const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(assign->assignee); if (id->name == p_identifier) { last_assign_line = assign->assigned_value->end_line; last_assigned_expression = assign->assigned_value; } } } break; default: // TODO: Check sub blocks (control flow statements) as they might also reassign stuff. break; } } if (suite->parent_if && suite->parent_if->condition && suite->parent_if->condition->type == GDScriptParser::Node::BINARY_OPERATOR && static_cast<const GDScriptParser::BinaryOpNode *>(suite->parent_if->condition)->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) { // Operator `is` used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common.. // Super dirty hack, but very useful. // Credit: Zylann. // TODO: this could be hacked to detect ANDed conditions too... const GDScriptParser::BinaryOpNode *op = static_cast<const GDScriptParser::BinaryOpNode *>(suite->parent_if->condition); if (op->left_operand && op->right_operand && op->left_operand->type == GDScriptParser::Node::IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(op->left_operand)->name == p_identifier) { // Bingo. GDScriptParser::CompletionContext c = p_context; c.current_line = op->left_operand->start_line; c.current_suite = suite; GDScriptCompletionIdentifier is_type; if (_guess_expression_type(c, op->right_operand, is_type)) { id_type = is_type.type; id_type.is_meta_type = false; if (last_assign_line < c.current_line) { // Override last assignment. last_assign_line = c.current_line; last_assigned_expression = nullptr; } } } } suite = suite->parent_block; } if (last_assigned_expression && last_assign_line < p_context.current_line) { GDScriptParser::CompletionContext c = p_context; c.current_line = last_assign_line; r_type.assigned_expression = last_assigned_expression; if (_guess_expression_type(c, last_assigned_expression, r_type)) { return true; } } 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()) { switch (base_type.kind) { case GDScriptParser::DataType::CLASS: if (base_type.class_type->has_function(p_context.current_function->identifier->name)) { GDScriptParser::FunctionNode *parent_function = base_type.class_type->get_member(p_context.current_function->identifier->name).function; if (parent_function->parameters_indices.has(p_identifier)) { const GDScriptParser::ParameterNode *parameter = parent_function->parameters[parent_function->parameters_indices[p_identifier]]; if ((!id_type.is_set() || id_type.is_variant()) && parameter->get_datatype().is_hard_type()) { id_type = parameter->get_datatype(); } if (parameter->initializer) { GDScriptParser::CompletionContext c = p_context; c.current_function = parent_function; c.current_class = base_type.class_type; c.base = nullptr; if (_guess_expression_type(c, parameter->initializer, r_type)) { return true; } } } } base_type = base_type.class_type->base_type; break; case GDScriptParser::DataType::NATIVE: { if (id_type.is_set() && !id_type.is_variant()) { base_type = GDScriptParser::DataType(); break; } MethodInfo 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); return true; } } } base_type = GDScriptParser::DataType(); } break; default: break; } } } if (id_type.is_set() && !id_type.is_variant()) { r_type.type = id_type; return true; } // Check current class (including inheritance). if (p_context.current_class) { GDScriptCompletionIdentifier base; base.value = p_context.base; base.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; base.type.kind = GDScriptParser::DataType::CLASS; base.type.class_type = p_context.current_class; base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static; if (_guess_identifier_type_from_base(p_context, base, p_identifier, r_type)) { return true; } } // Check global scripts. if (ScriptServer::is_global_class(p_identifier)) { String script = ScriptServer::get_global_class_path(p_identifier); if (script.to_lower().ends_with(".gd")) { Error err = OK; Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(script, GDScriptParserRef::INTERFACE_SOLVED, err); if (err == OK) { r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.script_path = script; r_type.type.class_type = parser->get_parser()->get_tree(); r_type.type.is_meta_type = true; r_type.type.is_constant = false; r_type.type.kind = GDScriptParser::DataType::CLASS; r_type.value = Variant(); p_context.dependent_parsers.push_back(parser); return true; } } else { Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier)); if (scr.is_valid()) { r_type = _type_from_variant(scr); r_type.type.is_meta_type = true; return true; } } return false; } // Check global variables (including autoloads). if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier)) { r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier]); return true; } // Check ClassDB. 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; if (Engine::get_singleton()->has_singleton(p_identifier)) { r_type.type.is_meta_type = false; r_type.value = Engine::get_singleton()->get_singleton_object(p_identifier); } else { r_type.type.is_meta_type = true; r_type.value = Variant(); } } return false; } static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) { GDScriptParser::DataType base_type = p_base.type; bool is_static = base_type.is_meta_type; while (base_type.is_set()) { switch (base_type.kind) { case GDScriptParser::DataType::CLASS: if (base_type.class_type->has_member(p_identifier)) { const GDScriptParser::ClassNode::Member &member = base_type.class_type->get_member(p_identifier); switch (member.type) { case GDScriptParser::ClassNode::Member::CONSTANT: r_type.type = member.constant->get_datatype(); if (member.constant->initializer && member.constant->initializer->is_constant) { r_type.value = member.constant->initializer->reduced_value; } return true; case GDScriptParser::ClassNode::Member::VARIABLE: if (!is_static) { if (member.variable->get_datatype().is_set() && !member.variable->get_datatype().is_variant()) { r_type.type = member.variable->get_datatype(); return true; } else if (member.variable->initializer) { const GDScriptParser::ExpressionNode *init = member.variable->initializer; if (init->is_constant) { r_type.value = init->reduced_value; r_type = _type_from_variant(init->reduced_value); return true; } else if (init->start_line == p_context.current_line) { return false; // Detects if variable is assigned to itself } else if (_is_expression_named_identifier(init, member.variable->identifier->name)) { if (member.variable->initializer->get_datatype().is_set()) { r_type.type = member.variable->initializer->get_datatype(); } else if (member.variable->get_datatype().is_set() && !member.variable->get_datatype().is_variant()) { r_type.type = member.variable->get_datatype(); } return true; } else if (_guess_expression_type(p_context, init, r_type)) { return true; } else if (init->get_datatype().is_set() && !init->get_datatype().is_variant()) { r_type.type = init->get_datatype(); return true; } } } // TODO: Check assignments in constructor. return false; case GDScriptParser::ClassNode::Member::ENUM: r_type.type = member.m_enum->get_datatype(); r_type.enumeration = member.m_enum->identifier->name; return true; case GDScriptParser::ClassNode::Member::ENUM_VALUE: r_type = _type_from_variant(member.enum_value.value); return true; case GDScriptParser::ClassNode::Member::SIGNAL: r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.kind = GDScriptParser::DataType::BUILTIN; r_type.type.builtin_type = Variant::SIGNAL; return true; case GDScriptParser::ClassNode::Member::FUNCTION: if (is_static && !member.function->is_static) { return false; } r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.kind = GDScriptParser::DataType::BUILTIN; r_type.type.builtin_type = Variant::CALLABLE; return true; case GDScriptParser::ClassNode::Member::CLASS: r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.kind = GDScriptParser::DataType::CLASS; r_type.type.class_type = member.m_class; r_type.type.is_meta_type = true; return true; case GDScriptParser::ClassNode::Member::GROUP: return false; // No-op, but silences warnings. case GDScriptParser::ClassNode::Member::UNDEFINED: return false; // Unreachable. } return false; } base_type = base_type.class_type->base_type; break; case GDScriptParser::DataType::SCRIPT: { Ref<Script> scr = base_type.script_type; if (scr.is_valid()) { HashMap<StringName, Variant> constants; scr->get_constants(&constants); if (constants.has(p_identifier)) { r_type = _type_from_variant(constants[p_identifier]); return true; } if (!is_static) { List<PropertyInfo> members; scr->get_script_property_list(&members); for (const PropertyInfo &prop : members) { if (prop.name == p_identifier) { r_type = _type_from_property(prop); return true; } } } Ref<Script> parent = scr->get_base_script(); if (parent.is_valid()) { base_type.script_type = parent; } else { base_type.kind = GDScriptParser::DataType::NATIVE; base_type.native_type = scr->get_instance_base_type(); } } else { return false; } } break; case GDScriptParser::DataType::NATIVE: { StringName class_name = base_type.native_type; if (!ClassDB::class_exists(class_name)) { return false; } // Skip constants since they're all integers. Type does not matter because int has no members. PropertyInfo prop; if (ClassDB::get_property_info(class_name, p_identifier, &prop)) { StringName getter = ClassDB::get_property_getter(class_name, p_identifier); if (getter != StringName()) { MethodBind *g = ClassDB::get_method(class_name, getter); if (g) { r_type = _type_from_property(g->get_return_info()); return true; } } else { r_type = _type_from_property(prop); return true; } } return false; } break; case GDScriptParser::DataType::BUILTIN: { Callable::CallError err; Variant tmp; Variant::construct(base_type.builtin_type, tmp, nullptr, 0, err); if (err.error != Callable::CallError::CALL_OK) { return false; } bool valid = false; Variant res = tmp.get(p_identifier, &valid); if (valid) { r_type = _type_from_variant(res); r_type.value = Variant(); r_type.type.is_constant = false; return true; } return false; } break; default: { return false; } break; } } return false; } static void _find_last_return_in_block(GDScriptParser::CompletionContext &p_context, int &r_last_return_line, const GDScriptParser::ExpressionNode **r_last_returned_value) { if (!p_context.current_suite) { return; } for (int i = 0; i < p_context.current_suite->statements.size(); i++) { if (p_context.current_suite->statements[i]->start_line < r_last_return_line) { break; } GDScriptParser::CompletionContext c = p_context; switch (p_context.current_suite->statements[i]->type) { case GDScriptParser::Node::FOR: c.current_suite = static_cast<const GDScriptParser::ForNode *>(p_context.current_suite->statements[i])->loop; _find_last_return_in_block(c, r_last_return_line, r_last_returned_value); break; case GDScriptParser::Node::WHILE: c.current_suite = static_cast<const GDScriptParser::WhileNode *>(p_context.current_suite->statements[i])->loop; _find_last_return_in_block(c, r_last_return_line, r_last_returned_value); break; case GDScriptParser::Node::IF: { const GDScriptParser::IfNode *_if = static_cast<const GDScriptParser::IfNode *>(p_context.current_suite->statements[i]); c.current_suite = _if->true_block; _find_last_return_in_block(c, r_last_return_line, r_last_returned_value); if (_if->false_block) { c.current_suite = _if->false_block; _find_last_return_in_block(c, r_last_return_line, r_last_returned_value); } } break; case GDScriptParser::Node::MATCH: { const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(p_context.current_suite->statements[i]); for (int j = 0; j < match->branches.size(); j++) { c.current_suite = match->branches[j]->block; _find_last_return_in_block(c, r_last_return_line, r_last_returned_value); } } break; case GDScriptParser::Node::RETURN: { const GDScriptParser::ReturnNode *ret = static_cast<const GDScriptParser::ReturnNode *>(p_context.current_suite->statements[i]); if (ret->return_value) { if (ret->start_line > r_last_return_line) { r_last_return_line = ret->start_line; *r_last_returned_value = ret->return_value; } } } break; default: break; } } } static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type) { GDScriptParser::DataType base_type = p_base.type; bool is_static = base_type.is_meta_type; if (is_static && p_method == SNAME("new")) { r_type.type = base_type; r_type.type.is_meta_type = false; r_type.type.is_constant = false; return true; } while (base_type.is_set() && !base_type.is_variant()) { switch (base_type.kind) { case GDScriptParser::DataType::CLASS: if (base_type.class_type->has_function(p_method)) { const GDScriptParser::FunctionNode *method = base_type.class_type->get_member(p_method).function; if (!is_static || method->is_static) { if (method->get_datatype().is_set() && !method->get_datatype().is_variant()) { r_type.type = method->get_datatype(); return true; } int last_return_line = -1; const GDScriptParser::ExpressionNode *last_returned_value = nullptr; GDScriptParser::CompletionContext c = p_context; c.current_class = base_type.class_type; c.current_function = const_cast<GDScriptParser::FunctionNode *>(method); c.current_suite = method->body; _find_last_return_in_block(c, last_return_line, &last_returned_value); if (last_returned_value) { c.current_line = c.current_suite->end_line; if (_guess_expression_type(c, last_returned_value, r_type)) { return true; } } } } base_type = base_type.class_type->base_type; break; case GDScriptParser::DataType::SCRIPT: { Ref<Script> scr = base_type.script_type; if (scr.is_valid()) { List<MethodInfo> methods; scr->get_script_method_list(&methods); for (const MethodInfo &mi : methods) { if (mi.name == p_method) { r_type = _type_from_property(mi.return_val); return true; } } Ref<Script> base_script = scr->get_base_script(); if (base_script.is_valid()) { base_type.script_type = base_script; } else { base_type.kind = GDScriptParser::DataType::NATIVE; base_type.native_type = scr->get_instance_base_type(); } } else { return false; } } break; case GDScriptParser::DataType::NATIVE: { if (!ClassDB::class_exists(base_type.native_type)) { return false; } MethodBind *mb = ClassDB::get_method(base_type.native_type, p_method); if (mb) { r_type = _type_from_property(mb->get_return_info()); return true; } return false; } break; case GDScriptParser::DataType::BUILTIN: { Callable::CallError err; Variant tmp; Variant::construct(base_type.builtin_type, tmp, nullptr, 0, err); if (err.error != Callable::CallError::CALL_OK) { return false; } List<MethodInfo> methods; tmp.get_method_list(&methods); for (const MethodInfo &mi : methods) { if (mi.name == p_method) { r_type = _type_from_property(mi.return_val); return true; } } return false; } break; default: { return false; } } } return false; } static void _find_enumeration_candidates(GDScriptParser::CompletionContext &p_context, const String &p_enum_hint, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result) { if (!p_enum_hint.contains(".")) { // Global constant or in the current class. StringName current_enum = p_enum_hint; if (p_context.current_class && p_context.current_class->has_member(current_enum) && p_context.current_class->get_member(current_enum).type == GDScriptParser::ClassNode::Member::ENUM) { const GDScriptParser::EnumNode *_enum = p_context.current_class->get_member(current_enum).m_enum; for (int i = 0; i < _enum->values.size(); i++) { ScriptLanguage::CodeCompletionOption option(_enum->values[i].identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_ENUM); r_result.insert(option.display, option); } } else { for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) { if (CoreConstants::get_global_constant_enum(i) == current_enum) { ScriptLanguage::CodeCompletionOption option(CoreConstants::get_global_constant_name(i), ScriptLanguage::CODE_COMPLETION_KIND_ENUM); r_result.insert(option.display, option); } } } } else { String class_name = p_enum_hint.get_slice(".", 0); String enum_name = p_enum_hint.get_slice(".", 1); if (!ClassDB::class_exists(class_name)) { return; } List<StringName> enum_constants; ClassDB::get_enum_constants(class_name, enum_name, &enum_constants); for (const StringName &E : enum_constants) { String candidate = class_name + "." + E; int location = _get_enum_constant_location(class_name, E); ScriptLanguage::CodeCompletionOption option(candidate, ScriptLanguage::CODE_COMPLETION_KIND_ENUM, location); r_result.insert(option.display, option); } } } static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, int p_argidx, bool p_static, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, String &r_arghint) { Variant base = p_base.value; GDScriptParser::DataType base_type = p_base.type; 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) { case GDScriptParser::DataType::CLASS: { if (base_type.class_type->has_member(p_method)) { const GDScriptParser::ClassNode::Member &member = base_type.class_type->get_member(p_method); if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) { r_arghint = _make_arguments_hint(member.function, p_argidx); return; } } base_type = base_type.class_type->base_type; } break; case GDScriptParser::DataType::NATIVE: { StringName class_name = base_type.native_type; if (!ClassDB::class_exists(class_name)) { base_type.kind = GDScriptParser::DataType::UNRESOLVED; break; } MethodInfo info; int method_args = 0; if (ClassDB::get_method_info(class_name, p_method, &info)) { method_args = info.arguments.size(); if (base.get_type() == Variant::OBJECT) { Object *obj = base.operator Object *(); if (obj) { List<String> options; obj->get_argument_options(p_method, p_argidx, &options); for (String &opt : options) { if (opt.is_quoted()) { opt = opt.unquote().quote(quote_style); // Handle user preference. } ScriptLanguage::CodeCompletionOption option(opt, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); r_result.insert(option.display, option); } } } if (p_argidx < method_args) { PropertyInfo arg_info = info.arguments[p_argidx]; if (arg_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) { _find_enumeration_candidates(p_context, arg_info.class_name, r_result); } } r_arghint = _make_arguments_hint(info, p_argidx); } if (p_argidx == 0 && ClassDB::is_parent_class(class_name, SNAME("Node")) && (p_method == SNAME("get_node") || p_method == SNAME("has_node"))) { // Get autoloads List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); for (const PropertyInfo &E : props) { String s = E.name; if (!s.begins_with("autoload/")) { continue; } String name = s.get_slice("/", 1); ScriptLanguage::CodeCompletionOption option("/root/" + name, ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH); option.insert_text = option.display.quote(quote_style); r_result.insert(option.display, option); } } if (p_argidx == 0 && method_args > 0 && ClassDB::is_parent_class(class_name, SNAME("InputEvent")) && p_method.operator String().contains("action")) { // Get input actions List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); for (const PropertyInfo &E : props) { String s = E.name; if (!s.begins_with("input/")) { continue; } String name = s.get_slice("/", 1); ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT); option.insert_text = option.display.quote(quote_style); r_result.insert(option.display, option); } } base_type.kind = GDScriptParser::DataType::UNRESOLVED; } break; case GDScriptParser::DataType::BUILTIN: { if (base.get_type() == Variant::NIL) { Callable::CallError err; Variant::construct(base_type.builtin_type, base, nullptr, 0, err); if (err.error != Callable::CallError::CALL_OK) { return; } } List<MethodInfo> methods; base.get_method_list(&methods); for (const MethodInfo &E : methods) { if (E.name == p_method) { r_arghint = _make_arguments_hint(E, p_argidx); return; } } base_type.kind = GDScriptParser::DataType::UNRESOLVED; } break; default: { base_type.kind = GDScriptParser::DataType::UNRESOLVED; } break; } } } static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::SubscriptNode *p_subscript, GDScriptParser::DataType &r_base_type, Variant *r_base = nullptr) { if (p_context.base == nullptr) { return false; } const GDScriptParser::GetNodeNode *get_node = nullptr; switch (p_subscript->base->type) { case GDScriptParser::Node::GET_NODE: { get_node = static_cast<GDScriptParser::GetNodeNode *>(p_subscript->base); } break; case GDScriptParser::Node::IDENTIFIER: { const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base); switch (identifier_node->source) { case GDScriptParser::IdentifierNode::Source::MEMBER_VARIABLE: { if (p_context.current_class != nullptr) { const StringName &member_name = identifier_node->name; const GDScriptParser::ClassNode *current_class = p_context.current_class; if (current_class->has_member(member_name)) { const GDScriptParser::ClassNode::Member &member = current_class->get_member(member_name); if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { const GDScriptParser::VariableNode *variable = static_cast<GDScriptParser::VariableNode *>(member.variable); if (variable->initializer && variable->initializer->type == GDScriptParser::Node::GET_NODE) { get_node = static_cast<GDScriptParser::GetNodeNode *>(variable->initializer); } } } } } break; case GDScriptParser::IdentifierNode::Source::LOCAL_VARIABLE: { if (identifier_node->next != nullptr && identifier_node->next->type == GDScriptParser::ClassNode::Node::GET_NODE) { get_node = static_cast<GDScriptParser::GetNodeNode *>(identifier_node->next); } } break; default: { } break; } } break; default: { } break; } if (get_node != nullptr) { const Object *node = p_context.base->call("get_node_or_null", NodePath(get_node->full_path)); if (node != nullptr) { if (r_base != nullptr) { *r_base = node; } r_base_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_base_type.kind = GDScriptParser::DataType::NATIVE; r_base_type.native_type = node->get_class_name(); r_base_type.builtin_type = Variant::OBJECT; return true; } } return false; } static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, const GDScriptParser::Node *p_call, int p_argidx, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, bool &r_forced, String &r_arghint) { if (p_call->type == GDScriptParser::Node::PRELOAD) { if (p_argidx == 0 && bool(EDITOR_GET("text_editor/completion/complete_file_paths"))) { _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), r_result); } MethodInfo mi(PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"), "preload", PropertyInfo(Variant::STRING, "path")); r_arghint = _make_arguments_hint(mi, p_argidx); return; } else if (p_call->type != GDScriptParser::Node::CALL) { return; } Variant base; GDScriptParser::DataType base_type; bool _static = false; const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_call); GDScriptParser::Node::Type callee_type = call->get_callee_type(); GDScriptCompletionIdentifier connect_base; if (callee_type == GDScriptParser::Node::SUBSCRIPT) { const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(call->callee); if (subscript->base != nullptr && subscript->base->type == GDScriptParser::Node::IDENTIFIER) { const GDScriptParser::IdentifierNode *base_identifier = static_cast<const GDScriptParser::IdentifierNode *>(subscript->base); Variant::Type method_type = GDScriptParser::get_builtin_type(base_identifier->name); if (method_type < Variant::VARIANT_MAX) { Variant v; Callable::CallError err; Variant::construct(method_type, v, nullptr, 0, err); if (err.error != Callable::CallError::CALL_OK) { return; } List<MethodInfo> methods; v.get_method_list(&methods); for (MethodInfo &E : methods) { if (p_argidx >= E.arguments.size()) { continue; } if (E.name == call->function_name) { r_arghint += _make_arguments_hint(E, p_argidx); return; } } } } if (subscript->is_attribute) { bool found_type = _get_subscript_type(p_context, subscript, base_type, &base); if (!found_type) { GDScriptCompletionIdentifier ci; if (_guess_expression_type(p_context, subscript->base, ci)) { base_type = ci.type; base = ci.value; } else { return; } } _static = base_type.is_meta_type; } } else if (Variant::has_utility_function(call->function_name)) { MethodInfo info = Variant::get_utility_function_info(call->function_name); r_arghint = _make_arguments_hint(info, p_argidx); return; } else if (GDScriptUtilityFunctions::function_exists(call->function_name)) { MethodInfo info = GDScriptUtilityFunctions::get_function_info(call->function_name); r_arghint = _make_arguments_hint(info, p_argidx); return; } else if (GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) { // Complete constructor. List<MethodInfo> constructors; Variant::get_constructor_list(GDScriptParser::get_builtin_type(call->function_name), &constructors); int i = 0; for (const MethodInfo &E : constructors) { if (p_argidx >= E.arguments.size()) { continue; } if (i > 0) { r_arghint += "\n"; } r_arghint += _make_arguments_hint(E, p_argidx); i++; } return; } else if (call->is_super || callee_type == GDScriptParser::Node::IDENTIFIER) { base = p_context.base; if (p_context.current_class) { base_type = p_context.current_class->get_datatype(); _static = !p_context.current_function || p_context.current_function->is_static; } } else { return; } GDScriptCompletionIdentifier ci; ci.type = base_type; ci.value = base; _find_call_arguments(p_context, ci, call->function_name, p_argidx, _static, r_result, r_arghint); r_forced = r_result.size() > 0; } ::Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) { const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; GDScriptParser parser; GDScriptAnalyzer analyzer(&parser); parser.parse(p_code, p_path, true); analyzer.analyze(); r_forced = false; HashMap<String, ScriptLanguage::CodeCompletionOption> options; GDScriptParser::CompletionContext completion_context = parser.get_completion_context(); completion_context.base = p_owner; bool is_function = false; switch (completion_context.type) { case GDScriptParser::COMPLETION_NONE: break; case GDScriptParser::COMPLETION_ANNOTATION: { List<MethodInfo> annotations; parser.get_annotation_list(&annotations); for (const MethodInfo &E : annotations) { ScriptLanguage::CodeCompletionOption option(E.name.substr(1), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); if (E.arguments.size() > 0) { option.insert_text += "("; } options.insert(option.display, option); } r_forced = true; } break; case GDScriptParser::COMPLETION_ANNOTATION_ARGUMENTS: { if (completion_context.node == nullptr || completion_context.node->type != GDScriptParser::Node::ANNOTATION) { break; } const GDScriptParser::AnnotationNode *annotation = static_cast<const GDScriptParser::AnnotationNode *>(completion_context.node); _find_annotation_arguments(annotation, completion_context.current_argument, quote_style, options); r_forced = true; } break; case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD: { // Constants. { List<StringName> constants; Variant::get_constants_for_type(completion_context.builtin_type, &constants); for (const StringName &E : constants) { ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT); bool valid = false; Variant default_value = Variant::get_constant_value(completion_context.builtin_type, E, &valid); if (valid) { option.default_value = default_value; } options.insert(option.display, option); } } // Methods. { List<StringName> methods; Variant::get_builtin_method_list(completion_context.builtin_type, &methods); for (const StringName &E : methods) { if (Variant::is_builtin_method_static(completion_context.builtin_type, E)) { ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); if (Variant::get_builtin_method_argument_count(completion_context.builtin_type, E) > 0 || Variant::is_builtin_method_vararg(completion_context.builtin_type, E)) { option.insert_text += "("; } else { option.insert_text += "()"; } options.insert(option.display, option); } } } } break; case GDScriptParser::COMPLETION_INHERIT_TYPE: { _list_available_types(true, completion_context, options); r_forced = true; } break; case GDScriptParser::COMPLETION_TYPE_NAME_OR_VOID: { ScriptLanguage::CodeCompletionOption option("void", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); options.insert(option.display, option); } [[fallthrough]]; case GDScriptParser::COMPLETION_TYPE_NAME: { _list_available_types(false, completion_context, options); r_forced = true; } break; case GDScriptParser::COMPLETION_PROPERTY_DECLARATION_OR_TYPE: { _list_available_types(false, completion_context, options); ScriptLanguage::CodeCompletionOption get("get", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); options.insert(get.display, get); ScriptLanguage::CodeCompletionOption set("set", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); options.insert(set.display, set); r_forced = true; } break; case GDScriptParser::COMPLETION_PROPERTY_DECLARATION: { ScriptLanguage::CodeCompletionOption get("get", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); options.insert(get.display, get); ScriptLanguage::CodeCompletionOption set("set", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); options.insert(set.display, set); r_forced = true; } break; case GDScriptParser::COMPLETION_PROPERTY_METHOD: { if (!completion_context.current_class) { break; } for (int i = 0; i < completion_context.current_class->members.size(); i++) { const GDScriptParser::ClassNode::Member &member = completion_context.current_class->members[i]; if (member.type != GDScriptParser::ClassNode::Member::FUNCTION) { continue; } if (member.function->is_static) { continue; } ScriptLanguage::CodeCompletionOption option(member.function->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); options.insert(option.display, option); } r_forced = true; } break; case GDScriptParser::COMPLETION_ASSIGN: { GDScriptCompletionIdentifier type; if (!completion_context.node || completion_context.node->type != GDScriptParser::Node::ASSIGNMENT) { break; } if (!_guess_expression_type(completion_context, static_cast<const GDScriptParser::AssignmentNode *>(completion_context.node)->assignee, type)) { _find_identifiers(completion_context, false, options, 0); r_forced = true; break; } if (!type.enumeration.is_empty()) { _find_enumeration_candidates(completion_context, type.enumeration, options); r_forced = options.size() > 0; } else { _find_identifiers(completion_context, false, options, 0); r_forced = true; } } break; case GDScriptParser::COMPLETION_METHOD: is_function = true; [[fallthrough]]; case GDScriptParser::COMPLETION_IDENTIFIER: { _find_identifiers(completion_context, is_function, options, 0); } break; case GDScriptParser::COMPLETION_ATTRIBUTE_METHOD: is_function = true; [[fallthrough]]; case GDScriptParser::COMPLETION_ATTRIBUTE: { r_forced = true; const GDScriptParser::SubscriptNode *attr = static_cast<const GDScriptParser::SubscriptNode *>(completion_context.node); if (attr->base) { GDScriptCompletionIdentifier base; bool found_type = _get_subscript_type(completion_context, attr, base.type); if (!found_type && !_guess_expression_type(completion_context, attr->base, base)) { break; } _find_identifiers_in_base(base, is_function, options, 0); } } break; case GDScriptParser::COMPLETION_SUBSCRIPT: { const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(completion_context.node); GDScriptCompletionIdentifier base; if (!_guess_expression_type(completion_context, subscript->base, base)) { break; } _find_identifiers_in_base(base, false, options, 0); } break; case GDScriptParser::COMPLETION_TYPE_ATTRIBUTE: { if (!completion_context.current_class) { break; } const GDScriptParser::TypeNode *type = static_cast<const GDScriptParser::TypeNode *>(completion_context.node); bool found = true; GDScriptCompletionIdentifier base; base.type.kind = GDScriptParser::DataType::CLASS; base.type.type_source = GDScriptParser::DataType::INFERRED; base.type.is_constant = true; base.type.class_type = completion_context.current_class; base.value = completion_context.base; for (int i = 0; i < completion_context.current_argument; i++) { GDScriptCompletionIdentifier ci; if (!_guess_identifier_type_from_base(completion_context, base, type->type_chain[i]->name, ci)) { found = false; break; } base = ci; } // TODO: Improve this to only list types. if (found) { _find_identifiers_in_base(base, false, options, 0); } r_forced = true; } break; case GDScriptParser::COMPLETION_RESOURCE_PATH: { if (EDITOR_GET("text_editor/completion/complete_file_paths")) { _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), options); r_forced = true; } } break; case GDScriptParser::COMPLETION_CALL_ARGUMENTS: { if (!completion_context.node) { break; } _find_call_arguments(completion_context, completion_context.node, completion_context.current_argument, options, r_forced, r_call_hint); } break; case GDScriptParser::COMPLETION_OVERRIDE_METHOD: { GDScriptParser::DataType native_type = completion_context.current_class->base_type; while (native_type.is_set() && native_type.kind != GDScriptParser::DataType::NATIVE) { switch (native_type.kind) { case GDScriptParser::DataType::CLASS: { native_type = native_type.class_type->base_type; } break; default: { native_type.kind = GDScriptParser::DataType::UNRESOLVED; } break; } } if (!native_type.is_set()) { break; } StringName class_name = native_type.native_type; if (!ClassDB::class_exists(class_name)) { break; } bool use_type_hint = EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints").operator bool(); List<MethodInfo> virtual_methods; ClassDB::get_virtual_methods(class_name, &virtual_methods); for (const MethodInfo &mi : virtual_methods) { String method_hint = mi.name; if (method_hint.contains(":")) { method_hint = method_hint.get_slice(":", 0); } method_hint += "("; if (mi.arguments.size()) { for (int i = 0; i < mi.arguments.size(); i++) { if (i > 0) { method_hint += ", "; } String arg = mi.arguments[i].name; if (arg.contains(":")) { arg = arg.substr(0, arg.find(":")); } method_hint += arg; if (use_type_hint && mi.arguments[i].type != Variant::NIL) { method_hint += ": "; if (mi.arguments[i].type == Variant::OBJECT && mi.arguments[i].class_name != StringName()) { method_hint += mi.arguments[i].class_name.operator String(); } else { method_hint += Variant::get_type_name(mi.arguments[i].type); } } } } method_hint += ")"; if (use_type_hint && (mi.return_val.type != Variant::NIL || !(mi.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) { method_hint += " -> "; if (mi.return_val.type == Variant::NIL) { method_hint += "void"; } else if (mi.return_val.type == Variant::OBJECT && mi.return_val.class_name != StringName()) { method_hint += mi.return_val.class_name.operator String(); } else { method_hint += Variant::get_type_name(mi.return_val.type); } } method_hint += ":"; ScriptLanguage::CodeCompletionOption option(method_hint, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); options.insert(option.display, option); } } 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()) { // 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()) { // Ignore quote_style and just use double quotes for paths with apostrophes. // Double quotes don't need to be checked because they're not valid in node and property names. opt = opt.quote(opt.contains("'") ? "\"" : quote_style); // Handle user preference. } ScriptLanguage::CodeCompletionOption option(opt, ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH); options.insert(option.display, option); } // Get autoloads. for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) { String path = "/root/" + E.key; ScriptLanguage::CodeCompletionOption option(path.quote(quote_style), ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH); options.insert(option.display, option); } } } break; case GDScriptParser::COMPLETION_SUPER_METHOD: { if (!completion_context.current_class) { break; } _find_identifiers_in_class(completion_context.current_class, true, false, true, options, 0); } break; } for (const KeyValue<String, ScriptLanguage::CodeCompletionOption> &E : options) { r_options->push_back(E.value); } return OK; } #else Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) { return OK; } #endif //////// END COMPLETION ////////// String GDScriptLanguage::_get_indentation() const { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { bool use_space_indentation = EDITOR_GET("text_editor/behavior/indent/type"); if (use_space_indentation) { int indent_size = EDITOR_GET("text_editor/behavior/indent/size"); String space_indent = ""; for (int i = 0; i < indent_size; i++) { space_indent += " "; } return space_indent; } } #endif return "\t"; } void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_to_line) const { String indent = _get_indentation(); Vector<String> lines = p_code.split("\n"); List<int> indent_stack; for (int i = 0; i < lines.size(); i++) { String l = lines[i]; int tc = 0; for (int j = 0; j < l.length(); j++) { if (l[j] == ' ' || l[j] == '\t') { tc++; } else { break; } } String st = l.substr(tc, l.length()).strip_edges(); if (st.is_empty() || st.begins_with("#")) { continue; //ignore! } int ilevel = 0; if (indent_stack.size()) { ilevel = indent_stack.back()->get(); } if (tc > ilevel) { indent_stack.push_back(tc); } else if (tc < ilevel) { while (indent_stack.size() && indent_stack.back()->get() > tc) { indent_stack.pop_back(); } if (indent_stack.size() && indent_stack.back()->get() != tc) { indent_stack.push_back(tc); // this is not right but gets the job done } } if (i >= p_from_line) { l = ""; for (int j = 0; j < indent_stack.size(); j++) { l += indent; } l += st; } else if (i > p_to_line) { break; } lines.write[i] = l; } p_code = ""; for (int i = 0; i < lines.size(); i++) { if (i > 0) { p_code += "\n"; } p_code += lines[i]; } } #ifdef TOOLS_ENABLED static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, const String &p_symbol, bool p_is_function, GDScriptLanguage::LookupResult &r_result) { GDScriptParser::DataType base_type = p_base; while (base_type.is_set()) { switch (base_type.kind) { case GDScriptParser::DataType::CLASS: { if (base_type.class_type) { if (base_type.class_type->has_member(p_symbol)) { r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION; r_result.location = base_type.class_type->get_member(p_symbol).get_line(); r_result.class_path = base_type.script_path; Error err = OK; r_result.script = GDScriptCache::get_shallow_script(r_result.class_path, err); return err; } base_type = base_type.class_type->base_type; } } break; case GDScriptParser::DataType::SCRIPT: { Ref<Script> scr = base_type.script_type; if (scr.is_valid()) { int line = scr->get_member_line(p_symbol); if (line >= 0) { r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION; r_result.location = line; r_result.script = scr; return OK; } Ref<Script> base_script = scr->get_base_script(); if (base_script.is_valid()) { base_type.script_type = base_script; } else { base_type.kind = GDScriptParser::DataType::NATIVE; base_type.native_type = scr->get_instance_base_type(); } } else { base_type.kind = GDScriptParser::DataType::UNRESOLVED; } } break; case GDScriptParser::DataType::NATIVE: { StringName class_name = base_type.native_type; if (!ClassDB::class_exists(class_name)) { base_type.kind = GDScriptParser::DataType::UNRESOLVED; break; } if (ClassDB::has_method(class_name, p_symbol, true)) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; r_result.class_name = base_type.native_type; r_result.class_member = p_symbol; return OK; } List<MethodInfo> virtual_methods; ClassDB::get_virtual_methods(class_name, &virtual_methods, true); for (const MethodInfo &E : virtual_methods) { if (E.name == p_symbol) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; r_result.class_name = base_type.native_type; r_result.class_member = p_symbol; return OK; } } if (ClassDB::has_signal(class_name, p_symbol, true)) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_SIGNAL; r_result.class_name = base_type.native_type; r_result.class_member = p_symbol; return OK; } StringName enum_name = ClassDB::get_integer_constant_enum(class_name, p_symbol, true); if (enum_name != StringName()) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM; r_result.class_name = base_type.native_type; r_result.class_member = enum_name; return OK; } List<String> constants; ClassDB::get_integer_constant_list(class_name, &constants, true); for (const String &E : constants) { if (E == p_symbol) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; r_result.class_name = base_type.native_type; r_result.class_member = p_symbol; return OK; } } if (ClassDB::has_property(class_name, p_symbol, true)) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY; r_result.class_name = base_type.native_type; r_result.class_member = p_symbol; return OK; } StringName parent = ClassDB::get_parent_class(class_name); if (parent != StringName()) { base_type.native_type = parent; } else { base_type.kind = GDScriptParser::DataType::UNRESOLVED; } } break; case GDScriptParser::DataType::BUILTIN: { base_type.kind = GDScriptParser::DataType::UNRESOLVED; if (Variant::has_constant(base_type.builtin_type, p_symbol)) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; r_result.class_name = Variant::get_type_name(base_type.builtin_type); r_result.class_member = p_symbol; return OK; } Variant v; Ref<RefCounted> v_ref; if (base_type.builtin_type == Variant::OBJECT) { v_ref.instantiate(); v = v_ref; } else { Callable::CallError err; Variant::construct(base_type.builtin_type, v, nullptr, 0, err); if (err.error != Callable::CallError::CALL_OK) { break; } } if (v.has_method(p_symbol)) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; r_result.class_name = Variant::get_type_name(base_type.builtin_type); r_result.class_member = p_symbol; return OK; } bool valid = false; v.get(p_symbol, &valid); if (valid) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY; r_result.class_name = Variant::get_type_name(base_type.builtin_type); r_result.class_member = p_symbol; return OK; } } break; default: { base_type.kind = GDScriptParser::DataType::UNRESOLVED; } break; } } return ERR_CANT_RESOLVE; } ::Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) { // Before parsing, try the usual stuff. if (ClassDB::class_exists(p_symbol)) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS; r_result.class_name = p_symbol; return OK; } for (int i = 0; i < Variant::VARIANT_MAX; i++) { Variant::Type t = Variant::Type(i); if (Variant::get_type_name(t) == p_symbol) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS; r_result.class_name = Variant::get_type_name(t); return OK; } } if ("PI" == p_symbol || "TAU" == p_symbol || "INF" == p_symbol || "NAN" == p_symbol) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; r_result.class_name = "@GDScript"; r_result.class_member = p_symbol; return OK; } GDScriptParser parser; parser.parse(p_code, p_path, true); GDScriptParser::CompletionContext context = parser.get_completion_context(); context.base = p_owner; // Allows class functions with the names like built-ins to be handled properly. if (context.type != GDScriptParser::COMPLETION_ATTRIBUTE) { // Need special checks for assert and preload as they are technically // keywords, so are not registered in GDScriptUtilityFunctions. if (GDScriptUtilityFunctions::function_exists(p_symbol) || "assert" == p_symbol || "preload" == p_symbol) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; r_result.class_name = "@GDScript"; r_result.class_member = p_symbol; return OK; } } GDScriptAnalyzer analyzer(&parser); analyzer.analyze(); if (context.current_class && context.current_class->extends.size() > 0) { bool success = false; ClassDB::get_integer_constant(context.current_class->extends[0], p_symbol, &success); if (success) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; r_result.class_name = context.current_class->extends[0]; r_result.class_member = p_symbol; return OK; } } bool is_function = false; switch (context.type) { case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD: { if (!Variant::has_builtin_method(context.builtin_type, StringName(p_symbol))) { // A constant. r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; r_result.class_name = Variant::get_type_name(context.builtin_type); r_result.class_member = p_symbol; return OK; } // A method. GDScriptParser::DataType base_type; base_type.kind = GDScriptParser::DataType::BUILTIN; base_type.builtin_type = context.builtin_type; if (_lookup_symbol_from_base(base_type, p_symbol, true, r_result) == OK) { return OK; } } break; case GDScriptParser::COMPLETION_SUPER_METHOD: case GDScriptParser::COMPLETION_METHOD: { is_function = true; [[fallthrough]]; } case GDScriptParser::COMPLETION_ASSIGN: case GDScriptParser::COMPLETION_CALL_ARGUMENTS: case GDScriptParser::COMPLETION_IDENTIFIER: { GDScriptParser::DataType base_type; if (context.current_class) { if (context.type != GDScriptParser::COMPLETION_SUPER_METHOD) { base_type = context.current_class->get_datatype(); } else { base_type = context.current_class->base_type; } } else { break; } if (!is_function && context.current_suite) { // Lookup local variables. const GDScriptParser::SuiteNode *suite = context.current_suite; while (suite) { if (suite->has_local(p_symbol)) { r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION; r_result.location = suite->get_local(p_symbol).start_line; return OK; } suite = suite->parent_block; } } if (_lookup_symbol_from_base(base_type, p_symbol, is_function, r_result) == OK) { return OK; } if (!is_function) { // Guess in autoloads as singletons. if (ProjectSettings::get_singleton()->has_autoload(p_symbol)) { const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(p_symbol); if (autoload.is_singleton) { String scr_path = autoload.path; if (!scr_path.ends_with(".gd")) { // Not a script, try find the script anyway, // may have some success. scr_path = scr_path.get_basename() + ".gd"; } if (FileAccess::exists(scr_path)) { r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION; r_result.location = 0; r_result.script = ResourceLoader::load(scr_path); return OK; } } } // Global. HashMap<StringName, int> classes = GDScriptLanguage::get_singleton()->get_global_map(); if (classes.has(p_symbol)) { Variant value = GDScriptLanguage::get_singleton()->get_global_array()[classes[p_symbol]]; if (value.get_type() == Variant::OBJECT) { Object *obj = value; if (obj) { if (Object::cast_to<GDScriptNativeClass>(obj)) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS; r_result.class_name = Object::cast_to<GDScriptNativeClass>(obj)->get_name(); } else { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS; r_result.class_name = obj->get_class(); } // proxy class remove the underscore. if (r_result.class_name.begins_with("_")) { r_result.class_name = r_result.class_name.substr(1); } return OK; } } else { /* // Because get_integer_constant_enum and get_integer_constant don't work on @GlobalScope // We cannot determine the exact nature of the identifier here // Otherwise these codes would work StringName enumName = ClassDB::get_integer_constant_enum("@GlobalScope", p_symbol, true); if (enumName != nullptr) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM; r_result.class_name = "@GlobalScope"; r_result.class_member = enumName; return OK; } else { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; r_result.class_name = "@GlobalScope"; r_result.class_member = p_symbol; return OK; }*/ r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE; r_result.class_name = "@GlobalScope"; 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::LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE; r_result.class_name = "@GlobalScope"; r_result.class_member = p_symbol; return OK; } } } } break; case GDScriptParser::COMPLETION_ATTRIBUTE_METHOD: { is_function = true; [[fallthrough]]; } case GDScriptParser::COMPLETION_ATTRIBUTE: { if (context.node->type != GDScriptParser::Node::SUBSCRIPT) { break; } const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(context.node); if (!subscript->is_attribute) { break; } GDScriptCompletionIdentifier base; bool found_type = _get_subscript_type(context, subscript, base.type); if (!found_type && !_guess_expression_type(context, subscript->base, base)) { break; } if (context.current_class) { if (context.type != GDScriptParser::COMPLETION_SUPER_METHOD) { base.type = context.current_class->get_datatype(); } else { base.type = context.current_class->base_type; } } if (_lookup_symbol_from_base(base.type, p_symbol, is_function, r_result) == OK) { return OK; } } break; case GDScriptParser::COMPLETION_OVERRIDE_METHOD: { GDScriptParser::DataType base_type = context.current_class->base_type; if (_lookup_symbol_from_base(base_type, p_symbol, true, r_result) == OK) { return OK; } } break; case GDScriptParser::COMPLETION_TYPE_NAME_OR_VOID: case GDScriptParser::COMPLETION_TYPE_NAME: { GDScriptParser::DataType base_type = context.current_class->get_datatype(); if (_lookup_symbol_from_base(base_type, p_symbol, false, r_result) == OK) { return OK; } } break; case GDScriptParser::COMPLETION_ANNOTATION: { const String annotation_symbol = "@" + p_symbol; if (parser.annotation_exists(annotation_symbol)) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ANNOTATION; r_result.class_name = "@GDScript"; r_result.class_member = annotation_symbol; return OK; } } break; default: { } } return ERR_CANT_RESOLVE; } #endif