| 1 | /**************************************************************************/ |
| 2 | /* gdscript_editor.cpp */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #include "gdscript.h" |
| 32 | |
| 33 | #include "gdscript_analyzer.h" |
| 34 | #include "gdscript_compiler.h" |
| 35 | #include "gdscript_parser.h" |
| 36 | #include "gdscript_tokenizer.h" |
| 37 | #include "gdscript_utility_functions.h" |
| 38 | |
| 39 | #ifdef TOOLS_ENABLED |
| 40 | #include "editor/script_templates/templates.gen.h" |
| 41 | #endif |
| 42 | |
| 43 | #include "core/config/engine.h" |
| 44 | #include "core/core_constants.h" |
| 45 | #include "core/io/file_access.h" |
| 46 | |
| 47 | #ifdef TOOLS_ENABLED |
| 48 | #include "core/config/project_settings.h" |
| 49 | #include "editor/editor_file_system.h" |
| 50 | #include "editor/editor_settings.h" |
| 51 | #endif |
| 52 | |
| 53 | void GDScriptLanguage::(List<String> *p_delimiters) const { |
| 54 | p_delimiters->push_back("#" ); |
| 55 | } |
| 56 | |
| 57 | void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const { |
| 58 | p_delimiters->push_back("\" \"" ); |
| 59 | p_delimiters->push_back("' '" ); |
| 60 | p_delimiters->push_back("\"\"\" \"\"\"" ); |
| 61 | p_delimiters->push_back("''' '''" ); |
| 62 | } |
| 63 | |
| 64 | bool GDScriptLanguage::is_using_templates() { |
| 65 | return true; |
| 66 | } |
| 67 | |
| 68 | Ref<Script> GDScriptLanguage::make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const { |
| 69 | Ref<GDScript> scr; |
| 70 | scr.instantiate(); |
| 71 | String processed_template = p_template; |
| 72 | bool type_hints = false; |
| 73 | #ifdef TOOLS_ENABLED |
| 74 | type_hints = EDITOR_GET("text_editor/completion/add_type_hints" ); |
| 75 | #endif |
| 76 | if (!type_hints) { |
| 77 | processed_template = processed_template.replace(": int" , "" ) |
| 78 | .replace(": String" , "" ) |
| 79 | .replace(": Array[String]" , "" ) |
| 80 | .replace(": float" , "" ) |
| 81 | .replace(": CharFXTransform" , "" ) |
| 82 | .replace(":=" , "=" ) |
| 83 | .replace(" -> String" , "" ) |
| 84 | .replace(" -> int" , "" ) |
| 85 | .replace(" -> bool" , "" ) |
| 86 | .replace(" -> void" , "" ); |
| 87 | } |
| 88 | |
| 89 | processed_template = processed_template.replace("_BASE_" , p_base_class_name) |
| 90 | .replace("_CLASS_" , p_class_name.to_pascal_case()) |
| 91 | .replace("_TS_" , _get_indentation()); |
| 92 | scr->set_source_code(processed_template); |
| 93 | return scr; |
| 94 | } |
| 95 | |
| 96 | Vector<ScriptLanguage::ScriptTemplate> GDScriptLanguage::get_built_in_templates(StringName p_object) { |
| 97 | Vector<ScriptLanguage::ScriptTemplate> templates; |
| 98 | #ifdef TOOLS_ENABLED |
| 99 | for (int i = 0; i < TEMPLATES_ARRAY_SIZE; i++) { |
| 100 | if (TEMPLATES[i].inherit == p_object) { |
| 101 | templates.append(TEMPLATES[i]); |
| 102 | } |
| 103 | } |
| 104 | #endif |
| 105 | return templates; |
| 106 | } |
| 107 | |
| 108 | static void get_function_names_recursively(const GDScriptParser::ClassNode *p_class, const String &p_prefix, HashMap<int, String> &r_funcs) { |
| 109 | for (int i = 0; i < p_class->members.size(); i++) { |
| 110 | if (p_class->members[i].type == GDScriptParser::ClassNode::Member::FUNCTION) { |
| 111 | const GDScriptParser::FunctionNode *function = p_class->members[i].function; |
| 112 | r_funcs[function->start_line] = p_prefix.is_empty() ? String(function->identifier->name) : p_prefix + "." + String(function->identifier->name); |
| 113 | } else if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) { |
| 114 | String new_prefix = p_class->members[i].m_class->identifier->name; |
| 115 | get_function_names_recursively(p_class->members[i].m_class, p_prefix.is_empty() ? new_prefix : p_prefix + "." + new_prefix, r_funcs); |
| 116 | } |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | 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 { |
| 121 | GDScriptParser parser; |
| 122 | GDScriptAnalyzer analyzer(&parser); |
| 123 | |
| 124 | Error err = parser.parse(p_script, p_path, false); |
| 125 | if (err == OK) { |
| 126 | err = analyzer.analyze(); |
| 127 | } |
| 128 | #ifdef DEBUG_ENABLED |
| 129 | if (r_warnings) { |
| 130 | for (const GDScriptWarning &E : parser.get_warnings()) { |
| 131 | const GDScriptWarning &warn = E; |
| 132 | ScriptLanguage::Warning w; |
| 133 | w.start_line = warn.start_line; |
| 134 | w.end_line = warn.end_line; |
| 135 | w.leftmost_column = warn.leftmost_column; |
| 136 | w.rightmost_column = warn.rightmost_column; |
| 137 | w.code = (int)warn.code; |
| 138 | w.string_code = GDScriptWarning::get_name_from_code(warn.code); |
| 139 | w.message = warn.get_message(); |
| 140 | r_warnings->push_back(w); |
| 141 | } |
| 142 | } |
| 143 | #endif |
| 144 | if (err) { |
| 145 | if (r_errors) { |
| 146 | for (const GDScriptParser::ParserError &pe : parser.get_errors()) { |
| 147 | ScriptLanguage::ScriptError e; |
| 148 | e.path = p_path; |
| 149 | e.line = pe.line; |
| 150 | e.column = pe.column; |
| 151 | e.message = pe.message; |
| 152 | r_errors->push_back(e); |
| 153 | } |
| 154 | |
| 155 | for (KeyValue<String, Ref<GDScriptParserRef>> E : analyzer.get_depended_parsers()) { |
| 156 | GDScriptParser *depended_parser = E.value->get_parser(); |
| 157 | for (const GDScriptParser::ParserError &pe : depended_parser->get_errors()) { |
| 158 | ScriptLanguage::ScriptError e; |
| 159 | e.path = E.key; |
| 160 | e.line = pe.line; |
| 161 | e.column = pe.column; |
| 162 | e.message = pe.message; |
| 163 | r_errors->push_back(e); |
| 164 | } |
| 165 | } |
| 166 | } |
| 167 | return false; |
| 168 | } else { |
| 169 | const GDScriptParser::ClassNode *cl = parser.get_tree(); |
| 170 | HashMap<int, String> funcs; |
| 171 | |
| 172 | get_function_names_recursively(cl, "" , funcs); |
| 173 | |
| 174 | for (const KeyValue<int, String> &E : funcs) { |
| 175 | r_functions->push_back(E.value + ":" + itos(E.key)); |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | #ifdef DEBUG_ENABLED |
| 180 | if (r_safe_lines) { |
| 181 | const HashSet<int> &unsafe_lines = parser.get_unsafe_lines(); |
| 182 | for (int i = 1; i <= parser.get_last_line_number(); i++) { |
| 183 | if (!unsafe_lines.has(i)) { |
| 184 | r_safe_lines->insert(i); |
| 185 | } |
| 186 | } |
| 187 | } |
| 188 | #endif |
| 189 | |
| 190 | return true; |
| 191 | } |
| 192 | |
| 193 | bool GDScriptLanguage::has_named_classes() const { |
| 194 | return false; |
| 195 | } |
| 196 | |
| 197 | bool GDScriptLanguage::supports_builtin_mode() const { |
| 198 | return true; |
| 199 | } |
| 200 | |
| 201 | bool GDScriptLanguage::supports_documentation() const { |
| 202 | return true; |
| 203 | } |
| 204 | |
| 205 | int GDScriptLanguage::find_function(const String &p_function, const String &p_code) const { |
| 206 | GDScriptTokenizer tokenizer; |
| 207 | tokenizer.set_source_code(p_code); |
| 208 | int indent = 0; |
| 209 | GDScriptTokenizer::Token current = tokenizer.scan(); |
| 210 | while (current.type != GDScriptTokenizer::Token::TK_EOF && current.type != GDScriptTokenizer::Token::ERROR) { |
| 211 | if (current.type == GDScriptTokenizer::Token::INDENT) { |
| 212 | indent++; |
| 213 | } else if (current.type == GDScriptTokenizer::Token::DEDENT) { |
| 214 | indent--; |
| 215 | } |
| 216 | if (indent == 0 && current.type == GDScriptTokenizer::Token::FUNC) { |
| 217 | current = tokenizer.scan(); |
| 218 | if (current.is_identifier()) { |
| 219 | String identifier = current.get_identifier(); |
| 220 | if (identifier == p_function) { |
| 221 | return current.start_line; |
| 222 | } |
| 223 | } |
| 224 | } |
| 225 | current = tokenizer.scan(); |
| 226 | } |
| 227 | return -1; |
| 228 | } |
| 229 | |
| 230 | Script *GDScriptLanguage::create_script() const { |
| 231 | return memnew(GDScript); |
| 232 | } |
| 233 | |
| 234 | /* DEBUGGER FUNCTIONS */ |
| 235 | |
| 236 | thread_local int GDScriptLanguage::_debug_parse_err_line = -1; |
| 237 | thread_local String GDScriptLanguage::_debug_parse_err_file; |
| 238 | thread_local String GDScriptLanguage::_debug_error; |
| 239 | |
| 240 | bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) { |
| 241 | // break because of parse error |
| 242 | |
| 243 | if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) { |
| 244 | _debug_parse_err_line = p_line; |
| 245 | _debug_parse_err_file = p_file; |
| 246 | _debug_error = p_error; |
| 247 | EngineDebugger::get_script_debugger()->debug(this, false, true); |
| 248 | // Because this is thread local, clear the memory afterwards. |
| 249 | _debug_parse_err_file = String(); |
| 250 | _debug_error = String(); |
| 251 | return true; |
| 252 | } else { |
| 253 | return false; |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | bool GDScriptLanguage::debug_break(const String &p_error, bool p_allow_continue) { |
| 258 | if (EngineDebugger::is_active()) { |
| 259 | _debug_parse_err_line = -1; |
| 260 | _debug_parse_err_file = "" ; |
| 261 | _debug_error = p_error; |
| 262 | bool is_error_breakpoint = p_error != "Breakpoint" ; |
| 263 | EngineDebugger::get_script_debugger()->debug(this, p_allow_continue, is_error_breakpoint); |
| 264 | // Because this is thread local, clear the memory afterwards. |
| 265 | _debug_parse_err_file = String(); |
| 266 | _debug_error = String(); |
| 267 | return true; |
| 268 | } else { |
| 269 | return false; |
| 270 | } |
| 271 | } |
| 272 | |
| 273 | String GDScriptLanguage::debug_get_error() const { |
| 274 | return _debug_error; |
| 275 | } |
| 276 | |
| 277 | int GDScriptLanguage::debug_get_stack_level_count() const { |
| 278 | if (_debug_parse_err_line >= 0) { |
| 279 | return 1; |
| 280 | } |
| 281 | |
| 282 | return _call_stack.stack_pos; |
| 283 | } |
| 284 | |
| 285 | int GDScriptLanguage::debug_get_stack_level_line(int p_level) const { |
| 286 | if (_debug_parse_err_line >= 0) { |
| 287 | return _debug_parse_err_line; |
| 288 | } |
| 289 | |
| 290 | ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, -1); |
| 291 | |
| 292 | int l = _call_stack.stack_pos - p_level - 1; |
| 293 | |
| 294 | return *(_call_stack.levels[l].line); |
| 295 | } |
| 296 | |
| 297 | String GDScriptLanguage::debug_get_stack_level_function(int p_level) const { |
| 298 | if (_debug_parse_err_line >= 0) { |
| 299 | return "" ; |
| 300 | } |
| 301 | |
| 302 | ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, "" ); |
| 303 | int l = _call_stack.stack_pos - p_level - 1; |
| 304 | return _call_stack.levels[l].function->get_name(); |
| 305 | } |
| 306 | |
| 307 | String GDScriptLanguage::debug_get_stack_level_source(int p_level) const { |
| 308 | if (_debug_parse_err_line >= 0) { |
| 309 | return _debug_parse_err_file; |
| 310 | } |
| 311 | |
| 312 | ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, "" ); |
| 313 | int l = _call_stack.stack_pos - p_level - 1; |
| 314 | return _call_stack.levels[l].function->get_source(); |
| 315 | } |
| 316 | |
| 317 | 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) { |
| 318 | if (_debug_parse_err_line >= 0) { |
| 319 | return; |
| 320 | } |
| 321 | |
| 322 | ERR_FAIL_INDEX(p_level, _call_stack.stack_pos); |
| 323 | int l = _call_stack.stack_pos - p_level - 1; |
| 324 | |
| 325 | GDScriptFunction *f = _call_stack.levels[l].function; |
| 326 | |
| 327 | List<Pair<StringName, int>> locals; |
| 328 | |
| 329 | f->debug_get_stack_member_state(*_call_stack.levels[l].line, &locals); |
| 330 | for (const Pair<StringName, int> &E : locals) { |
| 331 | p_locals->push_back(E.first); |
| 332 | p_values->push_back(_call_stack.levels[l].stack[E.second]); |
| 333 | } |
| 334 | } |
| 335 | |
| 336 | 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) { |
| 337 | if (_debug_parse_err_line >= 0) { |
| 338 | return; |
| 339 | } |
| 340 | |
| 341 | ERR_FAIL_INDEX(p_level, _call_stack.stack_pos); |
| 342 | int l = _call_stack.stack_pos - p_level - 1; |
| 343 | |
| 344 | GDScriptInstance *instance = _call_stack.levels[l].instance; |
| 345 | |
| 346 | if (!instance) { |
| 347 | return; |
| 348 | } |
| 349 | |
| 350 | Ref<GDScript> scr = instance->get_script(); |
| 351 | ERR_FAIL_COND(scr.is_null()); |
| 352 | |
| 353 | const HashMap<StringName, GDScript::MemberInfo> &mi = scr->debug_get_member_indices(); |
| 354 | |
| 355 | for (const KeyValue<StringName, GDScript::MemberInfo> &E : mi) { |
| 356 | p_members->push_back(E.key); |
| 357 | p_values->push_back(instance->debug_get_member_by_index(E.value.index)); |
| 358 | } |
| 359 | } |
| 360 | |
| 361 | ScriptInstance *GDScriptLanguage::debug_get_stack_level_instance(int p_level) { |
| 362 | if (_debug_parse_err_line >= 0) { |
| 363 | return nullptr; |
| 364 | } |
| 365 | |
| 366 | ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, nullptr); |
| 367 | |
| 368 | int l = _call_stack.stack_pos - p_level - 1; |
| 369 | ScriptInstance *instance = _call_stack.levels[l].instance; |
| 370 | |
| 371 | return instance; |
| 372 | } |
| 373 | |
| 374 | void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) { |
| 375 | const HashMap<StringName, int> &name_idx = GDScriptLanguage::get_singleton()->get_global_map(); |
| 376 | const Variant *gl_array = GDScriptLanguage::get_singleton()->get_global_array(); |
| 377 | |
| 378 | List<Pair<String, Variant>> cinfo; |
| 379 | get_public_constants(&cinfo); |
| 380 | |
| 381 | for (const KeyValue<StringName, int> &E : name_idx) { |
| 382 | if (ClassDB::class_exists(E.key) || Engine::get_singleton()->has_singleton(E.key)) { |
| 383 | continue; |
| 384 | } |
| 385 | |
| 386 | bool is_script_constant = false; |
| 387 | for (List<Pair<String, Variant>>::Element *CE = cinfo.front(); CE; CE = CE->next()) { |
| 388 | if (CE->get().first == E.key) { |
| 389 | is_script_constant = true; |
| 390 | break; |
| 391 | } |
| 392 | } |
| 393 | if (is_script_constant) { |
| 394 | continue; |
| 395 | } |
| 396 | |
| 397 | const Variant &var = gl_array[E.value]; |
| 398 | if (Object *obj = var) { |
| 399 | if (Object::cast_to<GDScriptNativeClass>(obj)) { |
| 400 | continue; |
| 401 | } |
| 402 | } |
| 403 | |
| 404 | bool skip = false; |
| 405 | for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) { |
| 406 | if (E.key == CoreConstants::get_global_constant_name(i)) { |
| 407 | skip = true; |
| 408 | break; |
| 409 | } |
| 410 | } |
| 411 | if (skip) { |
| 412 | continue; |
| 413 | } |
| 414 | |
| 415 | p_globals->push_back(E.key); |
| 416 | p_values->push_back(var); |
| 417 | } |
| 418 | } |
| 419 | |
| 420 | String GDScriptLanguage::debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) { |
| 421 | return "" ; |
| 422 | } |
| 423 | |
| 424 | void GDScriptLanguage::get_recognized_extensions(List<String> *p_extensions) const { |
| 425 | p_extensions->push_back("gd" ); |
| 426 | } |
| 427 | |
| 428 | void GDScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const { |
| 429 | List<StringName> functions; |
| 430 | GDScriptUtilityFunctions::get_function_list(&functions); |
| 431 | |
| 432 | for (const StringName &E : functions) { |
| 433 | p_functions->push_back(GDScriptUtilityFunctions::get_function_info(E)); |
| 434 | } |
| 435 | |
| 436 | // Not really "functions", but show in documentation. |
| 437 | { |
| 438 | MethodInfo mi; |
| 439 | mi.name = "preload" ; |
| 440 | mi.arguments.push_back(PropertyInfo(Variant::STRING, "path" )); |
| 441 | mi.return_val = PropertyInfo(Variant::OBJECT, "" , PROPERTY_HINT_RESOURCE_TYPE, "Resource" ); |
| 442 | p_functions->push_back(mi); |
| 443 | } |
| 444 | { |
| 445 | MethodInfo mi; |
| 446 | mi.name = "assert" ; |
| 447 | mi.return_val.type = Variant::NIL; |
| 448 | mi.arguments.push_back(PropertyInfo(Variant::BOOL, "condition" )); |
| 449 | mi.arguments.push_back(PropertyInfo(Variant::STRING, "message" )); |
| 450 | mi.default_arguments.push_back(String()); |
| 451 | p_functions->push_back(mi); |
| 452 | } |
| 453 | } |
| 454 | |
| 455 | void GDScriptLanguage::get_public_constants(List<Pair<String, Variant>> *p_constants) const { |
| 456 | Pair<String, Variant> pi; |
| 457 | pi.first = "PI" ; |
| 458 | pi.second = Math_PI; |
| 459 | p_constants->push_back(pi); |
| 460 | |
| 461 | Pair<String, Variant> tau; |
| 462 | tau.first = "TAU" ; |
| 463 | tau.second = Math_TAU; |
| 464 | p_constants->push_back(tau); |
| 465 | |
| 466 | Pair<String, Variant> infinity; |
| 467 | infinity.first = "INF" ; |
| 468 | infinity.second = INFINITY; |
| 469 | p_constants->push_back(infinity); |
| 470 | |
| 471 | Pair<String, Variant> nan; |
| 472 | nan.first = "NAN" ; |
| 473 | nan.second = NAN; |
| 474 | p_constants->push_back(nan); |
| 475 | } |
| 476 | |
| 477 | void GDScriptLanguage::get_public_annotations(List<MethodInfo> *p_annotations) const { |
| 478 | GDScriptParser parser; |
| 479 | List<MethodInfo> annotations; |
| 480 | parser.get_annotation_list(&annotations); |
| 481 | |
| 482 | for (const MethodInfo &E : annotations) { |
| 483 | p_annotations->push_back(E); |
| 484 | } |
| 485 | } |
| 486 | |
| 487 | String GDScriptLanguage::make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const { |
| 488 | #ifdef TOOLS_ENABLED |
| 489 | bool th = EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints" ); |
| 490 | #else |
| 491 | bool th = false; |
| 492 | #endif |
| 493 | |
| 494 | String s = "func " + p_name + "(" ; |
| 495 | if (p_args.size()) { |
| 496 | for (int i = 0; i < p_args.size(); i++) { |
| 497 | if (i > 0) { |
| 498 | s += ", " ; |
| 499 | } |
| 500 | s += p_args[i].get_slice(":" , 0); |
| 501 | if (th) { |
| 502 | String type = p_args[i].get_slice(":" , 1); |
| 503 | if (!type.is_empty() && type != "var" ) { |
| 504 | s += ": " + type; |
| 505 | } |
| 506 | } |
| 507 | } |
| 508 | } |
| 509 | s += String(")" ) + (th ? " -> void" : "" ) + ":\n" + _get_indentation() + "pass # Replace with function body.\n" ; |
| 510 | |
| 511 | return s; |
| 512 | } |
| 513 | |
| 514 | //////// COMPLETION ////////// |
| 515 | |
| 516 | #ifdef TOOLS_ENABLED |
| 517 | |
| 518 | #define COMPLETION_RECURSION_LIMIT 200 |
| 519 | |
| 520 | struct GDScriptCompletionIdentifier { |
| 521 | GDScriptParser::DataType type; |
| 522 | String enumeration; |
| 523 | Variant value; |
| 524 | const GDScriptParser::ExpressionNode *assigned_expression = nullptr; |
| 525 | }; |
| 526 | |
| 527 | // LOCATION METHODS |
| 528 | // These methods are used to populate the `CodeCompletionOption::location` integer. |
| 529 | // For these methods, the location is based on the depth in the inheritance chain that the property |
| 530 | // appears. For example, if you are completing code in a class that inherits Node2D, a property found on Node2D |
| 531 | // will have a "better" (lower) location "score" than a property that is found on CanvasItem. |
| 532 | |
| 533 | static int _get_property_location(StringName p_class, StringName p_property) { |
| 534 | if (!ClassDB::has_property(p_class, p_property)) { |
| 535 | return ScriptLanguage::LOCATION_OTHER; |
| 536 | } |
| 537 | |
| 538 | int depth = 0; |
| 539 | StringName class_test = p_class; |
| 540 | while (class_test && !ClassDB::has_property(class_test, p_property, true)) { |
| 541 | class_test = ClassDB::get_parent_class(class_test); |
| 542 | depth++; |
| 543 | } |
| 544 | |
| 545 | return depth | ScriptLanguage::LOCATION_PARENT_MASK; |
| 546 | } |
| 547 | |
| 548 | static int _get_constant_location(StringName p_class, StringName p_constant) { |
| 549 | if (!ClassDB::has_integer_constant(p_class, p_constant)) { |
| 550 | return ScriptLanguage::LOCATION_OTHER; |
| 551 | } |
| 552 | |
| 553 | int depth = 0; |
| 554 | StringName class_test = p_class; |
| 555 | while (class_test && !ClassDB::has_integer_constant(class_test, p_constant, true)) { |
| 556 | class_test = ClassDB::get_parent_class(class_test); |
| 557 | depth++; |
| 558 | } |
| 559 | |
| 560 | return depth | ScriptLanguage::LOCATION_PARENT_MASK; |
| 561 | } |
| 562 | |
| 563 | static int _get_signal_location(StringName p_class, StringName p_signal) { |
| 564 | if (!ClassDB::has_signal(p_class, p_signal)) { |
| 565 | return ScriptLanguage::LOCATION_OTHER; |
| 566 | } |
| 567 | |
| 568 | int depth = 0; |
| 569 | StringName class_test = p_class; |
| 570 | while (class_test && !ClassDB::has_signal(class_test, p_signal, true)) { |
| 571 | class_test = ClassDB::get_parent_class(class_test); |
| 572 | depth++; |
| 573 | } |
| 574 | |
| 575 | return depth | ScriptLanguage::LOCATION_PARENT_MASK; |
| 576 | } |
| 577 | |
| 578 | static int _get_method_location(StringName p_class, StringName p_method) { |
| 579 | if (!ClassDB::has_method(p_class, p_method)) { |
| 580 | return ScriptLanguage::LOCATION_OTHER; |
| 581 | } |
| 582 | |
| 583 | int depth = 0; |
| 584 | StringName class_test = p_class; |
| 585 | while (class_test && !ClassDB::has_method(class_test, p_method, true)) { |
| 586 | class_test = ClassDB::get_parent_class(class_test); |
| 587 | depth++; |
| 588 | } |
| 589 | |
| 590 | return depth | ScriptLanguage::LOCATION_PARENT_MASK; |
| 591 | } |
| 592 | |
| 593 | static int _get_enum_constant_location(StringName p_class, StringName p_enum_constant) { |
| 594 | if (!ClassDB::get_integer_constant_enum(p_class, p_enum_constant)) { |
| 595 | return ScriptLanguage::LOCATION_OTHER; |
| 596 | } |
| 597 | |
| 598 | int depth = 0; |
| 599 | StringName class_test = p_class; |
| 600 | while (class_test && !ClassDB::get_integer_constant_enum(class_test, p_enum_constant, true)) { |
| 601 | class_test = ClassDB::get_parent_class(class_test); |
| 602 | depth++; |
| 603 | } |
| 604 | |
| 605 | return depth | ScriptLanguage::LOCATION_PARENT_MASK; |
| 606 | } |
| 607 | |
| 608 | // END LOCATION METHODS |
| 609 | |
| 610 | static String _trim_parent_class(const String &p_class, const String &p_base_class) { |
| 611 | if (p_base_class.is_empty()) { |
| 612 | return p_class; |
| 613 | } |
| 614 | Vector<String> names = p_class.split("." , false, 1); |
| 615 | if (names.size() == 2) { |
| 616 | String first = names[0]; |
| 617 | String rest = names[1]; |
| 618 | if (ClassDB::class_exists(p_base_class) && ClassDB::class_exists(first) && ClassDB::is_parent_class(p_base_class, first)) { |
| 619 | return rest; |
| 620 | } |
| 621 | } |
| 622 | return p_class; |
| 623 | } |
| 624 | |
| 625 | static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg, const String &p_base_class = "" ) { |
| 626 | String class_name = p_info.class_name; |
| 627 | bool is_enum = p_info.type == Variant::INT && p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM; |
| 628 | // PROPERTY_USAGE_CLASS_IS_BITFIELD: BitField[T] isn't supported (yet?), use plain int. |
| 629 | |
| 630 | if ((p_info.type == Variant::OBJECT || is_enum) && !class_name.is_empty()) { |
| 631 | if (is_enum && CoreConstants::is_global_enum(p_info.class_name)) { |
| 632 | return class_name; |
| 633 | } |
| 634 | return _trim_parent_class(class_name, p_base_class); |
| 635 | } else if (p_info.type == Variant::ARRAY && p_info.hint == PROPERTY_HINT_ARRAY_TYPE && !p_info.hint_string.is_empty()) { |
| 636 | return "Array[" + _trim_parent_class(p_info.hint_string, p_base_class) + "]" ; |
| 637 | } else if (p_info.type == Variant::NIL) { |
| 638 | if (p_is_arg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) { |
| 639 | return "Variant" ; |
| 640 | } else { |
| 641 | return "void" ; |
| 642 | } |
| 643 | } |
| 644 | |
| 645 | return Variant::get_type_name(p_info.type); |
| 646 | } |
| 647 | |
| 648 | static String _make_arguments_hint(const MethodInfo &p_info, int p_arg_idx, bool p_is_annotation = false) { |
| 649 | String arghint; |
| 650 | if (!p_is_annotation) { |
| 651 | arghint += _get_visual_datatype(p_info.return_val, false) + " " ; |
| 652 | } |
| 653 | arghint += p_info.name + "(" ; |
| 654 | |
| 655 | int def_args = p_info.arguments.size() - p_info.default_arguments.size(); |
| 656 | int i = 0; |
| 657 | for (const PropertyInfo &E : p_info.arguments) { |
| 658 | if (i > 0) { |
| 659 | arghint += ", " ; |
| 660 | } |
| 661 | |
| 662 | if (i == p_arg_idx) { |
| 663 | arghint += String::chr(0xFFFF); |
| 664 | } |
| 665 | arghint += E.name + ": " + _get_visual_datatype(E, true); |
| 666 | |
| 667 | if (i - def_args >= 0) { |
| 668 | arghint += String(" = " ) + p_info.default_arguments[i - def_args].get_construct_string(); |
| 669 | } |
| 670 | |
| 671 | if (i == p_arg_idx) { |
| 672 | arghint += String::chr(0xFFFF); |
| 673 | } |
| 674 | |
| 675 | i++; |
| 676 | } |
| 677 | |
| 678 | if (p_info.flags & METHOD_FLAG_VARARG) { |
| 679 | if (p_info.arguments.size() > 0) { |
| 680 | arghint += ", " ; |
| 681 | } |
| 682 | if (p_arg_idx >= p_info.arguments.size()) { |
| 683 | arghint += String::chr(0xFFFF); |
| 684 | } |
| 685 | arghint += "..." ; |
| 686 | if (p_arg_idx >= p_info.arguments.size()) { |
| 687 | arghint += String::chr(0xFFFF); |
| 688 | } |
| 689 | } |
| 690 | |
| 691 | arghint += ")" ; |
| 692 | |
| 693 | return arghint; |
| 694 | } |
| 695 | |
| 696 | static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_function, int p_arg_idx) { |
| 697 | String arghint; |
| 698 | |
| 699 | if (p_function->get_datatype().builtin_type == Variant::NIL) { |
| 700 | arghint = "void " + p_function->identifier->name.operator String() + "(" ; |
| 701 | } else { |
| 702 | arghint = p_function->get_datatype().to_string() + " " + p_function->identifier->name.operator String() + "(" ; |
| 703 | } |
| 704 | |
| 705 | for (int i = 0; i < p_function->parameters.size(); i++) { |
| 706 | if (i > 0) { |
| 707 | arghint += ", " ; |
| 708 | } |
| 709 | |
| 710 | if (i == p_arg_idx) { |
| 711 | arghint += String::chr(0xFFFF); |
| 712 | } |
| 713 | const GDScriptParser::ParameterNode *par = p_function->parameters[i]; |
| 714 | if (!par->get_datatype().is_hard_type()) { |
| 715 | arghint += par->identifier->name.operator String() + ": Variant" ; |
| 716 | } else { |
| 717 | arghint += par->identifier->name.operator String() + ": " + par->get_datatype().to_string(); |
| 718 | } |
| 719 | |
| 720 | if (par->initializer) { |
| 721 | String def_val = "<unknown>" ; |
| 722 | switch (par->initializer->type) { |
| 723 | case GDScriptParser::Node::LITERAL: { |
| 724 | const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(par->initializer); |
| 725 | def_val = literal->value.get_construct_string(); |
| 726 | } break; |
| 727 | case GDScriptParser::Node::IDENTIFIER: { |
| 728 | const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(par->initializer); |
| 729 | def_val = id->name.operator String(); |
| 730 | } break; |
| 731 | case GDScriptParser::Node::CALL: { |
| 732 | const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(par->initializer); |
| 733 | if (call->is_constant && call->reduced) { |
| 734 | def_val = call->function_name.operator String() + call->reduced_value.operator String(); |
| 735 | } |
| 736 | } break; |
| 737 | case GDScriptParser::Node::ARRAY: { |
| 738 | const GDScriptParser::ArrayNode *arr = static_cast<const GDScriptParser::ArrayNode *>(par->initializer); |
| 739 | if (arr->is_constant && arr->reduced) { |
| 740 | def_val = arr->reduced_value.operator String(); |
| 741 | } |
| 742 | } break; |
| 743 | case GDScriptParser::Node::DICTIONARY: { |
| 744 | const GDScriptParser::DictionaryNode *dict = static_cast<const GDScriptParser::DictionaryNode *>(par->initializer); |
| 745 | if (dict->is_constant && dict->reduced) { |
| 746 | def_val = dict->reduced_value.operator String(); |
| 747 | } |
| 748 | } break; |
| 749 | case GDScriptParser::Node::SUBSCRIPT: { |
| 750 | const GDScriptParser::SubscriptNode *sub = static_cast<const GDScriptParser::SubscriptNode *>(par->initializer); |
| 751 | if (sub->is_constant) { |
| 752 | if (sub->datatype.kind == GDScriptParser::DataType::ENUM) { |
| 753 | def_val = sub->get_datatype().to_string(); |
| 754 | } else if (sub->reduced) { |
| 755 | const Variant::Type vt = sub->reduced_value.get_type(); |
| 756 | 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) { |
| 757 | def_val = sub->reduced_value.operator String(); |
| 758 | } else { |
| 759 | def_val = sub->get_datatype().to_string() + sub->reduced_value.operator String(); |
| 760 | } |
| 761 | } |
| 762 | } |
| 763 | } break; |
| 764 | default: |
| 765 | break; |
| 766 | } |
| 767 | arghint += " = " + def_val; |
| 768 | } |
| 769 | if (i == p_arg_idx) { |
| 770 | arghint += String::chr(0xFFFF); |
| 771 | } |
| 772 | } |
| 773 | |
| 774 | arghint += ")" ; |
| 775 | |
| 776 | return arghint; |
| 777 | } |
| 778 | |
| 779 | static void _get_directory_contents(EditorFileSystemDirectory *p_dir, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_list) { |
| 780 | const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes" ) ? "'" : "\"" ; |
| 781 | |
| 782 | for (int i = 0; i < p_dir->get_file_count(); i++) { |
| 783 | ScriptLanguage::CodeCompletionOption option(p_dir->get_file_path(i), ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH); |
| 784 | option.insert_text = option.display.quote(quote_style); |
| 785 | r_list.insert(option.display, option); |
| 786 | } |
| 787 | |
| 788 | for (int i = 0; i < p_dir->get_subdir_count(); i++) { |
| 789 | _get_directory_contents(p_dir->get_subdir(i), r_list); |
| 790 | } |
| 791 | } |
| 792 | |
| 793 | static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_annotation, int p_argument, const String p_quote_style, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result) { |
| 794 | if (p_annotation->name == SNAME("@export_range" )) { |
| 795 | if (p_argument == 3 || p_argument == 4 || p_argument == 5) { |
| 796 | // Slider hint. |
| 797 | ScriptLanguage::CodeCompletionOption slider1("or_greater" , ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); |
| 798 | slider1.insert_text = slider1.display.quote(p_quote_style); |
| 799 | r_result.insert(slider1.display, slider1); |
| 800 | ScriptLanguage::CodeCompletionOption slider2("or_less" , ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); |
| 801 | slider2.insert_text = slider2.display.quote(p_quote_style); |
| 802 | r_result.insert(slider2.display, slider2); |
| 803 | ScriptLanguage::CodeCompletionOption slider3("hide_slider" , ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); |
| 804 | slider3.insert_text = slider3.display.quote(p_quote_style); |
| 805 | r_result.insert(slider3.display, slider3); |
| 806 | } |
| 807 | } else if (p_annotation->name == SNAME("@export_exp_easing" )) { |
| 808 | if (p_argument == 0 || p_argument == 1) { |
| 809 | // Easing hint. |
| 810 | ScriptLanguage::CodeCompletionOption hint1("attenuation" , ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); |
| 811 | hint1.insert_text = hint1.display.quote(p_quote_style); |
| 812 | r_result.insert(hint1.display, hint1); |
| 813 | ScriptLanguage::CodeCompletionOption hint2("inout" , ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); |
| 814 | hint2.insert_text = hint2.display.quote(p_quote_style); |
| 815 | r_result.insert(hint2.display, hint2); |
| 816 | } |
| 817 | } else if (p_annotation->name == SNAME("@export_node_path" )) { |
| 818 | ScriptLanguage::CodeCompletionOption node("Node" , ScriptLanguage::CODE_COMPLETION_KIND_CLASS); |
| 819 | node.insert_text = node.display.quote(p_quote_style); |
| 820 | r_result.insert(node.display, node); |
| 821 | |
| 822 | List<StringName> native_classes; |
| 823 | ClassDB::get_inheriters_from_class("Node" , &native_classes); |
| 824 | for (const StringName &E : native_classes) { |
| 825 | if (!ClassDB::is_class_exposed(E)) { |
| 826 | continue; |
| 827 | } |
| 828 | ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS); |
| 829 | option.insert_text = option.display.quote(p_quote_style); |
| 830 | r_result.insert(option.display, option); |
| 831 | } |
| 832 | |
| 833 | List<StringName> global_script_classes; |
| 834 | ScriptServer::get_global_class_list(&global_script_classes); |
| 835 | for (const StringName &E : global_script_classes) { |
| 836 | if (!ClassDB::is_parent_class(ScriptServer::get_global_class_native_base(E), "Node" )) { |
| 837 | continue; |
| 838 | } |
| 839 | ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS); |
| 840 | option.insert_text = option.display.quote(p_quote_style); |
| 841 | r_result.insert(option.display, option); |
| 842 | } |
| 843 | } else if (p_annotation->name == SNAME("@warning_ignore" )) { |
| 844 | for (int warning_code = 0; warning_code < GDScriptWarning::WARNING_MAX; warning_code++) { |
| 845 | ScriptLanguage::CodeCompletionOption warning(GDScriptWarning::get_name_from_code((GDScriptWarning::Code)warning_code).to_lower(), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); |
| 846 | warning.insert_text = warning.display.quote(p_quote_style); |
| 847 | r_result.insert(warning.display, warning); |
| 848 | } |
| 849 | } else if (p_annotation->name == SNAME("@rpc" )) { |
| 850 | if (p_argument == 0 || p_argument == 1 || p_argument == 2) { |
| 851 | static const char *options[7] = { "call_local" , "call_remote" , "any_peer" , "authority" , "reliable" , "unreliable" , "unreliable_ordered" }; |
| 852 | for (int i = 0; i < 7; i++) { |
| 853 | ScriptLanguage::CodeCompletionOption option(options[i], ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); |
| 854 | option.insert_text = option.display.quote(p_quote_style); |
| 855 | r_result.insert(option.display, option); |
| 856 | } |
| 857 | } |
| 858 | } |
| 859 | } |
| 860 | |
| 861 | static void _find_built_in_variants(HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, bool exclude_nil = false) { |
| 862 | for (int i = 0; i < Variant::VARIANT_MAX; i++) { |
| 863 | if (!exclude_nil && Variant::Type(i) == Variant::Type::NIL) { |
| 864 | ScriptLanguage::CodeCompletionOption option("null" , ScriptLanguage::CODE_COMPLETION_KIND_CLASS); |
| 865 | r_result.insert(option.display, option); |
| 866 | } else { |
| 867 | ScriptLanguage::CodeCompletionOption option(Variant::get_type_name(Variant::Type(i)), ScriptLanguage::CODE_COMPLETION_KIND_CLASS); |
| 868 | r_result.insert(option.display, option); |
| 869 | } |
| 870 | } |
| 871 | } |
| 872 | |
| 873 | static void _list_available_types(bool p_inherit_only, GDScriptParser::CompletionContext &p_context, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result) { |
| 874 | // Built-in Variant Types |
| 875 | _find_built_in_variants(r_result, true); |
| 876 | |
| 877 | List<StringName> native_types; |
| 878 | ClassDB::get_class_list(&native_types); |
| 879 | for (const StringName &E : native_types) { |
| 880 | if (ClassDB::is_class_exposed(E) && !Engine::get_singleton()->has_singleton(E)) { |
| 881 | ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS); |
| 882 | r_result.insert(option.display, option); |
| 883 | } |
| 884 | } |
| 885 | |
| 886 | if (p_context.current_class) { |
| 887 | if (!p_inherit_only && p_context.current_class->base_type.is_set()) { |
| 888 | // Native enums from base class |
| 889 | List<StringName> enums; |
| 890 | ClassDB::get_enum_list(p_context.current_class->base_type.native_type, &enums); |
| 891 | for (const StringName &E : enums) { |
| 892 | ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_ENUM); |
| 893 | r_result.insert(option.display, option); |
| 894 | } |
| 895 | } |
| 896 | // Check current class for potential types |
| 897 | const GDScriptParser::ClassNode *current = p_context.current_class; |
| 898 | while (current) { |
| 899 | for (int i = 0; i < current->members.size(); i++) { |
| 900 | const GDScriptParser::ClassNode::Member &member = current->members[i]; |
| 901 | switch (member.type) { |
| 902 | case GDScriptParser::ClassNode::Member::CLASS: { |
| 903 | ScriptLanguage::CodeCompletionOption option(member.m_class->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_LOCAL); |
| 904 | r_result.insert(option.display, option); |
| 905 | } break; |
| 906 | case GDScriptParser::ClassNode::Member::ENUM: { |
| 907 | if (!p_inherit_only) { |
| 908 | ScriptLanguage::CodeCompletionOption option(member.m_enum->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_ENUM, ScriptLanguage::LOCATION_LOCAL); |
| 909 | r_result.insert(option.display, option); |
| 910 | } |
| 911 | } break; |
| 912 | case GDScriptParser::ClassNode::Member::CONSTANT: { |
| 913 | if (member.constant->get_datatype().is_meta_type && p_context.current_class->outer != nullptr) { |
| 914 | ScriptLanguage::CodeCompletionOption option(member.constant->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_LOCAL); |
| 915 | r_result.insert(option.display, option); |
| 916 | } |
| 917 | } break; |
| 918 | default: |
| 919 | break; |
| 920 | } |
| 921 | } |
| 922 | current = current->outer; |
| 923 | } |
| 924 | } |
| 925 | |
| 926 | // Global scripts |
| 927 | List<StringName> global_classes; |
| 928 | ScriptServer::get_global_class_list(&global_classes); |
| 929 | for (const StringName &E : global_classes) { |
| 930 | ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_OTHER_USER_CODE); |
| 931 | r_result.insert(option.display, option); |
| 932 | } |
| 933 | |
| 934 | // Autoload singletons |
| 935 | HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); |
| 936 | |
| 937 | for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : autoloads) { |
| 938 | const ProjectSettings::AutoloadInfo &info = E.value; |
| 939 | if (!info.is_singleton || info.path.get_extension().to_lower() != "gd" ) { |
| 940 | continue; |
| 941 | } |
| 942 | ScriptLanguage::CodeCompletionOption option(info.name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_OTHER_USER_CODE); |
| 943 | r_result.insert(option.display, option); |
| 944 | } |
| 945 | } |
| 946 | |
| 947 | static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth = 0) { |
| 948 | for (int i = 0; i < p_suite->locals.size(); i++) { |
| 949 | ScriptLanguage::CodeCompletionOption option; |
| 950 | int location = p_recursion_depth == 0 ? ScriptLanguage::LOCATION_LOCAL : (p_recursion_depth | ScriptLanguage::LOCATION_PARENT_MASK); |
| 951 | if (p_suite->locals[i].type == GDScriptParser::SuiteNode::Local::CONSTANT) { |
| 952 | option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); |
| 953 | option.default_value = p_suite->locals[i].constant->initializer->reduced_value; |
| 954 | } else { |
| 955 | option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE, location); |
| 956 | } |
| 957 | r_result.insert(option.display, option); |
| 958 | } |
| 959 | if (p_suite->parent_block) { |
| 960 | _find_identifiers_in_suite(p_suite->parent_block, r_result, p_recursion_depth + 1); |
| 961 | } |
| 962 | } |
| 963 | |
| 964 | static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth); |
| 965 | |
| 966 | 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) { |
| 967 | ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT); |
| 968 | |
| 969 | if (!p_parent_only) { |
| 970 | bool outer = false; |
| 971 | const GDScriptParser::ClassNode *clss = p_class; |
| 972 | int classes_processed = 0; |
| 973 | while (clss) { |
| 974 | for (int i = 0; i < clss->members.size(); i++) { |
| 975 | const int location = p_recursion_depth == 0 ? classes_processed : (p_recursion_depth | ScriptLanguage::LOCATION_PARENT_MASK); |
| 976 | const GDScriptParser::ClassNode::Member &member = clss->members[i]; |
| 977 | ScriptLanguage::CodeCompletionOption option; |
| 978 | switch (member.type) { |
| 979 | case GDScriptParser::ClassNode::Member::VARIABLE: |
| 980 | if (p_only_functions || outer || (p_static)) { |
| 981 | continue; |
| 982 | } |
| 983 | option = ScriptLanguage::CodeCompletionOption(member.variable->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); |
| 984 | break; |
| 985 | case GDScriptParser::ClassNode::Member::CONSTANT: |
| 986 | if (p_only_functions) { |
| 987 | continue; |
| 988 | } |
| 989 | if (r_result.has(member.constant->identifier->name)) { |
| 990 | continue; |
| 991 | } |
| 992 | option = ScriptLanguage::CodeCompletionOption(member.constant->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); |
| 993 | if (member.constant->initializer) { |
| 994 | option.default_value = member.constant->initializer->reduced_value; |
| 995 | } |
| 996 | break; |
| 997 | case GDScriptParser::ClassNode::Member::CLASS: |
| 998 | if (p_only_functions) { |
| 999 | continue; |
| 1000 | } |
| 1001 | option = ScriptLanguage::CodeCompletionOption(member.m_class->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, location); |
| 1002 | break; |
| 1003 | case GDScriptParser::ClassNode::Member::ENUM_VALUE: |
| 1004 | if (p_only_functions) { |
| 1005 | continue; |
| 1006 | } |
| 1007 | option = ScriptLanguage::CodeCompletionOption(member.enum_value.identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); |
| 1008 | break; |
| 1009 | case GDScriptParser::ClassNode::Member::ENUM: |
| 1010 | if (p_only_functions) { |
| 1011 | continue; |
| 1012 | } |
| 1013 | option = ScriptLanguage::CodeCompletionOption(member.m_enum->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_ENUM, location); |
| 1014 | break; |
| 1015 | case GDScriptParser::ClassNode::Member::FUNCTION: |
| 1016 | if (outer || (p_static && !member.function->is_static) || member.function->identifier->name.operator String().begins_with("@" )) { |
| 1017 | continue; |
| 1018 | } |
| 1019 | option = ScriptLanguage::CodeCompletionOption(member.function->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location); |
| 1020 | if (member.function->parameters.size() > 0) { |
| 1021 | option.insert_text += "(" ; |
| 1022 | } else { |
| 1023 | option.insert_text += "()" ; |
| 1024 | } |
| 1025 | break; |
| 1026 | case GDScriptParser::ClassNode::Member::SIGNAL: |
| 1027 | if (p_only_functions || outer || p_static) { |
| 1028 | continue; |
| 1029 | } |
| 1030 | option = ScriptLanguage::CodeCompletionOption(member.signal->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); |
| 1031 | break; |
| 1032 | case GDScriptParser::ClassNode::Member::GROUP: |
| 1033 | break; // No-op, but silences warnings. |
| 1034 | case GDScriptParser::ClassNode::Member::UNDEFINED: |
| 1035 | break; |
| 1036 | } |
| 1037 | r_result.insert(option.display, option); |
| 1038 | } |
| 1039 | outer = true; |
| 1040 | clss = clss->outer; |
| 1041 | classes_processed++; |
| 1042 | } |
| 1043 | } |
| 1044 | |
| 1045 | // Parents. |
| 1046 | GDScriptCompletionIdentifier base_type; |
| 1047 | base_type.type = p_class->base_type; |
| 1048 | base_type.type.is_meta_type = p_static; |
| 1049 | |
| 1050 | _find_identifiers_in_base(base_type, p_only_functions, r_result, p_recursion_depth + 1); |
| 1051 | } |
| 1052 | |
| 1053 | static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth) { |
| 1054 | ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT); |
| 1055 | |
| 1056 | GDScriptParser::DataType base_type = p_base.type; |
| 1057 | |
| 1058 | if (base_type.is_meta_type && base_type.kind != GDScriptParser::DataType::BUILTIN && base_type.kind != GDScriptParser::DataType::ENUM) { |
| 1059 | ScriptLanguage::CodeCompletionOption option("new" , ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, ScriptLanguage::LOCATION_LOCAL); |
| 1060 | option.insert_text += "(" ; |
| 1061 | r_result.insert(option.display, option); |
| 1062 | } |
| 1063 | |
| 1064 | while (!base_type.has_no_type()) { |
| 1065 | switch (base_type.kind) { |
| 1066 | case GDScriptParser::DataType::CLASS: { |
| 1067 | _find_identifiers_in_class(base_type.class_type, p_only_functions, base_type.is_meta_type, false, r_result, p_recursion_depth); |
| 1068 | // This already finds all parent identifiers, so we are done. |
| 1069 | base_type = GDScriptParser::DataType(); |
| 1070 | } break; |
| 1071 | case GDScriptParser::DataType::SCRIPT: { |
| 1072 | Ref<Script> scr = base_type.script_type; |
| 1073 | if (scr.is_valid()) { |
| 1074 | if (!p_only_functions) { |
| 1075 | if (!base_type.is_meta_type) { |
| 1076 | List<PropertyInfo> members; |
| 1077 | scr->get_script_property_list(&members); |
| 1078 | for (const PropertyInfo &E : members) { |
| 1079 | int location = p_recursion_depth + _get_property_location(scr->get_class_name(), E.name); |
| 1080 | ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); |
| 1081 | r_result.insert(option.display, option); |
| 1082 | } |
| 1083 | |
| 1084 | List<MethodInfo> signals; |
| 1085 | scr->get_script_signal_list(&signals); |
| 1086 | for (const MethodInfo &E : signals) { |
| 1087 | int location = p_recursion_depth + _get_signal_location(scr->get_class_name(), E.name); |
| 1088 | ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); |
| 1089 | r_result.insert(option.display, option); |
| 1090 | } |
| 1091 | } |
| 1092 | HashMap<StringName, Variant> constants; |
| 1093 | scr->get_constants(&constants); |
| 1094 | for (const KeyValue<StringName, Variant> &E : constants) { |
| 1095 | int location = p_recursion_depth + _get_constant_location(scr->get_class_name(), E.key); |
| 1096 | ScriptLanguage::CodeCompletionOption option(E.key.operator String(), ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); |
| 1097 | r_result.insert(option.display, option); |
| 1098 | } |
| 1099 | } |
| 1100 | |
| 1101 | List<MethodInfo> methods; |
| 1102 | scr->get_script_method_list(&methods); |
| 1103 | for (const MethodInfo &E : methods) { |
| 1104 | if (E.name.begins_with("@" )) { |
| 1105 | continue; |
| 1106 | } |
| 1107 | int location = p_recursion_depth + _get_method_location(scr->get_class_name(), E.name); |
| 1108 | ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location); |
| 1109 | if (E.arguments.size()) { |
| 1110 | option.insert_text += "(" ; |
| 1111 | } else { |
| 1112 | option.insert_text += "()" ; |
| 1113 | } |
| 1114 | r_result.insert(option.display, option); |
| 1115 | } |
| 1116 | |
| 1117 | Ref<Script> base_script = scr->get_base_script(); |
| 1118 | if (base_script.is_valid()) { |
| 1119 | base_type.script_type = base_script; |
| 1120 | } else { |
| 1121 | base_type.kind = GDScriptParser::DataType::NATIVE; |
| 1122 | base_type.native_type = scr->get_instance_base_type(); |
| 1123 | } |
| 1124 | } else { |
| 1125 | return; |
| 1126 | } |
| 1127 | } break; |
| 1128 | case GDScriptParser::DataType::NATIVE: { |
| 1129 | StringName type = base_type.native_type; |
| 1130 | if (!ClassDB::class_exists(type)) { |
| 1131 | return; |
| 1132 | } |
| 1133 | |
| 1134 | if (!p_only_functions) { |
| 1135 | List<String> constants; |
| 1136 | ClassDB::get_integer_constant_list(type, &constants); |
| 1137 | for (const String &E : constants) { |
| 1138 | int location = p_recursion_depth + _get_constant_location(type, StringName(E)); |
| 1139 | ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); |
| 1140 | r_result.insert(option.display, option); |
| 1141 | } |
| 1142 | |
| 1143 | if (!base_type.is_meta_type || Engine::get_singleton()->has_singleton(type)) { |
| 1144 | List<PropertyInfo> pinfo; |
| 1145 | ClassDB::get_property_list(type, &pinfo); |
| 1146 | for (const PropertyInfo &E : pinfo) { |
| 1147 | if (E.usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) { |
| 1148 | continue; |
| 1149 | } |
| 1150 | if (E.name.contains("/" )) { |
| 1151 | continue; |
| 1152 | } |
| 1153 | int location = p_recursion_depth + _get_property_location(type, E.name); |
| 1154 | ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); |
| 1155 | r_result.insert(option.display, option); |
| 1156 | } |
| 1157 | |
| 1158 | List<MethodInfo> signals; |
| 1159 | ClassDB::get_signal_list(type, &signals); |
| 1160 | for (const MethodInfo &E : signals) { |
| 1161 | int location = p_recursion_depth + _get_signal_location(type, StringName(E.name)); |
| 1162 | ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); |
| 1163 | r_result.insert(option.display, option); |
| 1164 | } |
| 1165 | } |
| 1166 | } |
| 1167 | |
| 1168 | bool only_static = base_type.is_meta_type && !Engine::get_singleton()->has_singleton(type); |
| 1169 | |
| 1170 | List<MethodInfo> methods; |
| 1171 | ClassDB::get_method_list(type, &methods, false, true); |
| 1172 | for (const MethodInfo &E : methods) { |
| 1173 | if (only_static && (E.flags & METHOD_FLAG_STATIC) == 0) { |
| 1174 | continue; |
| 1175 | } |
| 1176 | if (E.name.begins_with("_" )) { |
| 1177 | continue; |
| 1178 | } |
| 1179 | int location = p_recursion_depth + _get_method_location(type, E.name); |
| 1180 | ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location); |
| 1181 | if (E.arguments.size()) { |
| 1182 | option.insert_text += "(" ; |
| 1183 | } else { |
| 1184 | option.insert_text += "()" ; |
| 1185 | } |
| 1186 | r_result.insert(option.display, option); |
| 1187 | } |
| 1188 | return; |
| 1189 | } break; |
| 1190 | case GDScriptParser::DataType::ENUM: |
| 1191 | case GDScriptParser::DataType::BUILTIN: { |
| 1192 | Callable::CallError err; |
| 1193 | Variant tmp; |
| 1194 | Variant::construct(base_type.builtin_type, tmp, nullptr, 0, err); |
| 1195 | if (err.error != Callable::CallError::CALL_OK) { |
| 1196 | return; |
| 1197 | } |
| 1198 | |
| 1199 | if (!p_only_functions) { |
| 1200 | List<PropertyInfo> members; |
| 1201 | if (p_base.value.get_type() != Variant::NIL) { |
| 1202 | p_base.value.get_property_list(&members); |
| 1203 | } else { |
| 1204 | tmp.get_property_list(&members); |
| 1205 | } |
| 1206 | |
| 1207 | for (const PropertyInfo &E : members) { |
| 1208 | if (!String(E.name).contains("/" )) { |
| 1209 | ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER); |
| 1210 | if (GDScriptParser::theme_color_names.has(E.name)) { |
| 1211 | option.theme_color_name = GDScriptParser::theme_color_names[E.name]; |
| 1212 | } |
| 1213 | r_result.insert(option.display, option); |
| 1214 | } |
| 1215 | } |
| 1216 | } |
| 1217 | |
| 1218 | List<MethodInfo> methods; |
| 1219 | tmp.get_method_list(&methods); |
| 1220 | for (const MethodInfo &E : methods) { |
| 1221 | if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type && !(E.flags & METHOD_FLAG_CONST)) { |
| 1222 | // Enum types are static and cannot change, therefore we skip non-const dictionary methods. |
| 1223 | continue; |
| 1224 | } |
| 1225 | ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); |
| 1226 | if (E.arguments.size()) { |
| 1227 | option.insert_text += "(" ; |
| 1228 | } else { |
| 1229 | option.insert_text += "()" ; |
| 1230 | } |
| 1231 | r_result.insert(option.display, option); |
| 1232 | } |
| 1233 | |
| 1234 | return; |
| 1235 | } break; |
| 1236 | default: { |
| 1237 | return; |
| 1238 | } break; |
| 1239 | } |
| 1240 | } |
| 1241 | } |
| 1242 | |
| 1243 | static void _find_identifiers(const GDScriptParser::CompletionContext &p_context, bool p_only_functions, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth) { |
| 1244 | if (!p_only_functions && p_context.current_suite) { |
| 1245 | // This includes function parameters, since they are also locals. |
| 1246 | _find_identifiers_in_suite(p_context.current_suite, r_result); |
| 1247 | } |
| 1248 | |
| 1249 | if (p_context.current_class) { |
| 1250 | _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); |
| 1251 | } |
| 1252 | |
| 1253 | List<StringName> functions; |
| 1254 | GDScriptUtilityFunctions::get_function_list(&functions); |
| 1255 | |
| 1256 | for (const StringName &E : functions) { |
| 1257 | MethodInfo function = GDScriptUtilityFunctions::get_function_info(E); |
| 1258 | ScriptLanguage::CodeCompletionOption option(String(E), ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); |
| 1259 | if (function.arguments.size() || (function.flags & METHOD_FLAG_VARARG)) { |
| 1260 | option.insert_text += "(" ; |
| 1261 | } else { |
| 1262 | option.insert_text += "()" ; |
| 1263 | } |
| 1264 | r_result.insert(option.display, option); |
| 1265 | } |
| 1266 | |
| 1267 | if (p_only_functions) { |
| 1268 | return; |
| 1269 | } |
| 1270 | |
| 1271 | _find_built_in_variants(r_result); |
| 1272 | |
| 1273 | static const char *_keywords[] = { |
| 1274 | "true" , "false" , "PI" , "TAU" , "INF" , "NAN" , "null" , "self" , "super" , |
| 1275 | "break" , "breakpoint" , "continue" , "pass" , "return" , |
| 1276 | nullptr |
| 1277 | }; |
| 1278 | |
| 1279 | const char **kw = _keywords; |
| 1280 | while (*kw) { |
| 1281 | ScriptLanguage::CodeCompletionOption option(*kw, ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); |
| 1282 | r_result.insert(option.display, option); |
| 1283 | kw++; |
| 1284 | } |
| 1285 | |
| 1286 | static const char *_keywords_with_space[] = { |
| 1287 | "and" , "not" , "or" , "in" , "as" , "class" , "class_name" , "extends" , "is" , "func" , "signal" , "await" , |
| 1288 | "const" , "enum" , "static" , "var" , "if" , "elif" , "else" , "for" , "match" , "while" , |
| 1289 | nullptr |
| 1290 | }; |
| 1291 | |
| 1292 | const char **kws = _keywords_with_space; |
| 1293 | while (*kws) { |
| 1294 | ScriptLanguage::CodeCompletionOption option(*kws, ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); |
| 1295 | option.insert_text += " " ; |
| 1296 | r_result.insert(option.display, option); |
| 1297 | kws++; |
| 1298 | } |
| 1299 | |
| 1300 | static const char *_keywords_with_args[] = { |
| 1301 | "assert" , "preload" , |
| 1302 | nullptr |
| 1303 | }; |
| 1304 | |
| 1305 | const char **kwa = _keywords_with_args; |
| 1306 | while (*kwa) { |
| 1307 | ScriptLanguage::CodeCompletionOption option(*kwa, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); |
| 1308 | option.insert_text += "(" ; |
| 1309 | r_result.insert(option.display, option); |
| 1310 | kwa++; |
| 1311 | } |
| 1312 | |
| 1313 | List<StringName> utility_func_names; |
| 1314 | Variant::get_utility_function_list(&utility_func_names); |
| 1315 | |
| 1316 | for (List<StringName>::Element *E = utility_func_names.front(); E; E = E->next()) { |
| 1317 | ScriptLanguage::CodeCompletionOption option(E->get(), ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); |
| 1318 | option.insert_text += "(" ; |
| 1319 | r_result.insert(option.display, option); |
| 1320 | } |
| 1321 | |
| 1322 | for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) { |
| 1323 | if (!E.value.is_singleton) { |
| 1324 | continue; |
| 1325 | } |
| 1326 | ScriptLanguage::CodeCompletionOption option(E.key, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT); |
| 1327 | r_result.insert(option.display, option); |
| 1328 | } |
| 1329 | |
| 1330 | // Native classes and global constants. |
| 1331 | for (const KeyValue<StringName, int> &E : GDScriptLanguage::get_singleton()->get_global_map()) { |
| 1332 | ScriptLanguage::CodeCompletionOption option; |
| 1333 | if (ClassDB::class_exists(E.key) || Engine::get_singleton()->has_singleton(E.key)) { |
| 1334 | option = ScriptLanguage::CodeCompletionOption(E.key.operator String(), ScriptLanguage::CODE_COMPLETION_KIND_CLASS); |
| 1335 | } else { |
| 1336 | option = ScriptLanguage::CodeCompletionOption(E.key.operator String(), ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT); |
| 1337 | } |
| 1338 | r_result.insert(option.display, option); |
| 1339 | } |
| 1340 | |
| 1341 | // Global classes |
| 1342 | List<StringName> global_classes; |
| 1343 | ScriptServer::get_global_class_list(&global_classes); |
| 1344 | for (const StringName &E : global_classes) { |
| 1345 | ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_OTHER_USER_CODE); |
| 1346 | r_result.insert(option.display, option); |
| 1347 | } |
| 1348 | } |
| 1349 | |
| 1350 | static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value) { |
| 1351 | GDScriptCompletionIdentifier ci; |
| 1352 | ci.value = p_value; |
| 1353 | ci.type.is_constant = true; |
| 1354 | ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; |
| 1355 | ci.type.kind = GDScriptParser::DataType::BUILTIN; |
| 1356 | ci.type.builtin_type = p_value.get_type(); |
| 1357 | |
| 1358 | if (ci.type.builtin_type == Variant::OBJECT) { |
| 1359 | Object *obj = p_value.operator Object *(); |
| 1360 | if (!obj) { |
| 1361 | return ci; |
| 1362 | } |
| 1363 | ci.type.native_type = obj->get_class_name(); |
| 1364 | Ref<Script> scr = p_value; |
| 1365 | if (scr.is_valid()) { |
| 1366 | ci.type.is_meta_type = true; |
| 1367 | } else { |
| 1368 | ci.type.is_meta_type = false; |
| 1369 | scr = obj->get_script(); |
| 1370 | } |
| 1371 | if (scr.is_valid()) { |
| 1372 | ci.type.script_type = scr; |
| 1373 | ci.type.kind = GDScriptParser::DataType::SCRIPT; |
| 1374 | ci.type.native_type = scr->get_instance_base_type(); |
| 1375 | } else { |
| 1376 | ci.type.kind = GDScriptParser::DataType::NATIVE; |
| 1377 | } |
| 1378 | } |
| 1379 | |
| 1380 | return ci; |
| 1381 | } |
| 1382 | |
| 1383 | static GDScriptCompletionIdentifier _type_from_property(const PropertyInfo &p_property) { |
| 1384 | GDScriptCompletionIdentifier ci; |
| 1385 | |
| 1386 | if (p_property.type == Variant::NIL) { |
| 1387 | // Variant |
| 1388 | return ci; |
| 1389 | } |
| 1390 | |
| 1391 | if (p_property.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) { |
| 1392 | ci.enumeration = p_property.class_name; |
| 1393 | } |
| 1394 | |
| 1395 | ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; |
| 1396 | ci.type.builtin_type = p_property.type; |
| 1397 | if (p_property.type == Variant::OBJECT) { |
| 1398 | ci.type.kind = GDScriptParser::DataType::NATIVE; |
| 1399 | ci.type.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; |
| 1400 | } else { |
| 1401 | ci.type.kind = GDScriptParser::DataType::BUILTIN; |
| 1402 | } |
| 1403 | return ci; |
| 1404 | } |
| 1405 | |
| 1406 | #define MAX_COMPLETION_RECURSION 100 |
| 1407 | struct RecursionCheck { |
| 1408 | int *counter; |
| 1409 | _FORCE_INLINE_ bool check() { |
| 1410 | return (*counter) > MAX_COMPLETION_RECURSION; |
| 1411 | } |
| 1412 | RecursionCheck(int *p_counter) : |
| 1413 | counter(p_counter) { |
| 1414 | (*counter)++; |
| 1415 | } |
| 1416 | ~RecursionCheck() { |
| 1417 | (*counter)--; |
| 1418 | } |
| 1419 | }; |
| 1420 | |
| 1421 | static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::IdentifierNode *p_identifier, GDScriptCompletionIdentifier &r_type); |
| 1422 | static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); |
| 1423 | static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type); |
| 1424 | |
| 1425 | static bool _is_expression_named_identifier(const GDScriptParser::ExpressionNode *p_expression, const StringName &p_name) { |
| 1426 | if (p_expression) { |
| 1427 | switch (p_expression->type) { |
| 1428 | case GDScriptParser::Node::IDENTIFIER: { |
| 1429 | const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(p_expression); |
| 1430 | if (id->name == p_name) { |
| 1431 | return true; |
| 1432 | } |
| 1433 | } break; |
| 1434 | case GDScriptParser::Node::CAST: { |
| 1435 | const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); |
| 1436 | return _is_expression_named_identifier(cn->operand, p_name); |
| 1437 | } break; |
| 1438 | default: |
| 1439 | break; |
| 1440 | } |
| 1441 | } |
| 1442 | |
| 1443 | return false; |
| 1444 | } |
| 1445 | |
| 1446 | static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::ExpressionNode *p_expression, GDScriptCompletionIdentifier &r_type) { |
| 1447 | bool found = false; |
| 1448 | |
| 1449 | if (p_expression == nullptr) { |
| 1450 | return false; |
| 1451 | } |
| 1452 | |
| 1453 | static int recursion_depth = 0; |
| 1454 | RecursionCheck recursion(&recursion_depth); |
| 1455 | if (unlikely(recursion.check())) { |
| 1456 | ERR_FAIL_V_MSG(false, "Reached recursion limit while trying to guess type." ); |
| 1457 | } |
| 1458 | |
| 1459 | if (p_expression->is_constant) { |
| 1460 | // Already has a value, so just use that. |
| 1461 | r_type = _type_from_variant(p_expression->reduced_value); |
| 1462 | switch (p_expression->get_datatype().kind) { |
| 1463 | case GDScriptParser::DataType::ENUM: |
| 1464 | case GDScriptParser::DataType::CLASS: |
| 1465 | r_type.type = p_expression->get_datatype(); |
| 1466 | break; |
| 1467 | default: |
| 1468 | break; |
| 1469 | } |
| 1470 | found = true; |
| 1471 | } else { |
| 1472 | switch (p_expression->type) { |
| 1473 | case GDScriptParser::Node::LITERAL: { |
| 1474 | const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(p_expression); |
| 1475 | r_type = _type_from_variant(literal->value); |
| 1476 | found = true; |
| 1477 | } break; |
| 1478 | case GDScriptParser::Node::SELF: { |
| 1479 | if (p_context.current_class) { |
| 1480 | if (p_context.type != GDScriptParser::COMPLETION_SUPER_METHOD) { |
| 1481 | r_type.type = p_context.current_class->get_datatype(); |
| 1482 | } else { |
| 1483 | r_type.type = p_context.current_class->base_type; |
| 1484 | } |
| 1485 | found = true; |
| 1486 | } |
| 1487 | } break; |
| 1488 | case GDScriptParser::Node::IDENTIFIER: { |
| 1489 | const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(p_expression); |
| 1490 | found = _guess_identifier_type(p_context, id, r_type); |
| 1491 | } break; |
| 1492 | case GDScriptParser::Node::DICTIONARY: { |
| 1493 | // Try to recreate the dictionary. |
| 1494 | const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(p_expression); |
| 1495 | Dictionary d; |
| 1496 | bool full = true; |
| 1497 | for (int i = 0; i < dn->elements.size(); i++) { |
| 1498 | GDScriptCompletionIdentifier key; |
| 1499 | if (_guess_expression_type(p_context, dn->elements[i].key, key)) { |
| 1500 | if (!key.type.is_constant) { |
| 1501 | full = false; |
| 1502 | break; |
| 1503 | } |
| 1504 | GDScriptCompletionIdentifier value; |
| 1505 | if (_guess_expression_type(p_context, dn->elements[i].value, value)) { |
| 1506 | if (!value.type.is_constant) { |
| 1507 | full = false; |
| 1508 | break; |
| 1509 | } |
| 1510 | d[key.value] = value.value; |
| 1511 | } else { |
| 1512 | full = false; |
| 1513 | break; |
| 1514 | } |
| 1515 | } else { |
| 1516 | full = false; |
| 1517 | break; |
| 1518 | } |
| 1519 | } |
| 1520 | if (full) { |
| 1521 | r_type.value = d; |
| 1522 | r_type.type.is_constant = true; |
| 1523 | } |
| 1524 | r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; |
| 1525 | r_type.type.kind = GDScriptParser::DataType::BUILTIN; |
| 1526 | r_type.type.builtin_type = Variant::DICTIONARY; |
| 1527 | found = true; |
| 1528 | } break; |
| 1529 | case GDScriptParser::Node::ARRAY: { |
| 1530 | // Try to recreate the array |
| 1531 | const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_expression); |
| 1532 | Array a; |
| 1533 | bool full = true; |
| 1534 | a.resize(an->elements.size()); |
| 1535 | for (int i = 0; i < an->elements.size(); i++) { |
| 1536 | GDScriptCompletionIdentifier value; |
| 1537 | if (_guess_expression_type(p_context, an->elements[i], value)) { |
| 1538 | if (value.type.is_constant) { |
| 1539 | a[i] = value.value; |
| 1540 | } else { |
| 1541 | full = false; |
| 1542 | break; |
| 1543 | } |
| 1544 | } else { |
| 1545 | full = false; |
| 1546 | break; |
| 1547 | } |
| 1548 | } |
| 1549 | if (full) { |
| 1550 | // If not fully constant, setting this value is detrimental to the inference. |
| 1551 | r_type.value = a; |
| 1552 | } |
| 1553 | r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; |
| 1554 | r_type.type.kind = GDScriptParser::DataType::BUILTIN; |
| 1555 | r_type.type.builtin_type = Variant::ARRAY; |
| 1556 | found = true; |
| 1557 | } break; |
| 1558 | case GDScriptParser::Node::CAST: { |
| 1559 | const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); |
| 1560 | GDScriptCompletionIdentifier value; |
| 1561 | if (_guess_expression_type(p_context, cn->operand, r_type)) { |
| 1562 | r_type.type = cn->get_datatype(); |
| 1563 | found = true; |
| 1564 | } |
| 1565 | } break; |
| 1566 | case GDScriptParser::Node::CALL: { |
| 1567 | const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_expression); |
| 1568 | if (GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) { |
| 1569 | r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; |
| 1570 | r_type.type.kind = GDScriptParser::DataType::BUILTIN; |
| 1571 | r_type.type.builtin_type = GDScriptParser::get_builtin_type(call->function_name); |
| 1572 | found = true; |
| 1573 | break; |
| 1574 | } else if (GDScriptUtilityFunctions::function_exists(call->function_name)) { |
| 1575 | MethodInfo mi = GDScriptUtilityFunctions::get_function_info(call->function_name); |
| 1576 | r_type = _type_from_property(mi.return_val); |
| 1577 | found = true; |
| 1578 | break; |
| 1579 | } else { |
| 1580 | GDScriptParser::CompletionContext c = p_context; |
| 1581 | c.current_line = call->start_line; |
| 1582 | |
| 1583 | GDScriptParser::Node::Type callee_type = call->get_callee_type(); |
| 1584 | |
| 1585 | GDScriptCompletionIdentifier base; |
| 1586 | if (callee_type == GDScriptParser::Node::IDENTIFIER || call->is_super) { |
| 1587 | // Simple call, so base is 'self'. |
| 1588 | if (p_context.current_class) { |
| 1589 | if (call->is_super) { |
| 1590 | base.type = p_context.current_class->base_type; |
| 1591 | base.value = p_context.base; |
| 1592 | } else { |
| 1593 | base.type.kind = GDScriptParser::DataType::CLASS; |
| 1594 | base.type.type_source = GDScriptParser::DataType::INFERRED; |
| 1595 | base.type.is_constant = true; |
| 1596 | base.type.class_type = p_context.current_class; |
| 1597 | base.value = p_context.base; |
| 1598 | } |
| 1599 | } else { |
| 1600 | break; |
| 1601 | } |
| 1602 | } else if (callee_type == GDScriptParser::Node::SUBSCRIPT && static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->is_attribute) { |
| 1603 | if (!_guess_expression_type(c, static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->base, base)) { |
| 1604 | found = false; |
| 1605 | break; |
| 1606 | } |
| 1607 | } else { |
| 1608 | break; |
| 1609 | } |
| 1610 | |
| 1611 | // Try call if constant methods with constant arguments |
| 1612 | if (base.type.is_constant && base.value.get_type() == Variant::OBJECT) { |
| 1613 | GDScriptParser::DataType native_type = base.type; |
| 1614 | |
| 1615 | while (native_type.kind == GDScriptParser::DataType::CLASS) { |
| 1616 | native_type = native_type.class_type->base_type; |
| 1617 | } |
| 1618 | |
| 1619 | while (native_type.kind == GDScriptParser::DataType::SCRIPT) { |
| 1620 | if (native_type.script_type.is_valid()) { |
| 1621 | Ref<Script> parent = native_type.script_type->get_base_script(); |
| 1622 | if (parent.is_valid()) { |
| 1623 | native_type.script_type = parent; |
| 1624 | } else { |
| 1625 | native_type.kind = GDScriptParser::DataType::NATIVE; |
| 1626 | native_type.native_type = native_type.script_type->get_instance_base_type(); |
| 1627 | if (!ClassDB::class_exists(native_type.native_type)) { |
| 1628 | native_type.kind = GDScriptParser::DataType::UNRESOLVED; |
| 1629 | } |
| 1630 | } |
| 1631 | } |
| 1632 | } |
| 1633 | |
| 1634 | if (native_type.kind == GDScriptParser::DataType::NATIVE) { |
| 1635 | MethodBind *mb = ClassDB::get_method(native_type.native_type, call->function_name); |
| 1636 | if (mb && mb->is_const()) { |
| 1637 | bool all_is_const = true; |
| 1638 | Vector<Variant> args; |
| 1639 | GDScriptParser::CompletionContext c2 = p_context; |
| 1640 | c2.current_line = call->start_line; |
| 1641 | for (int i = 0; all_is_const && i < call->arguments.size(); i++) { |
| 1642 | GDScriptCompletionIdentifier arg; |
| 1643 | |
| 1644 | if (!call->arguments[i]->is_constant) { |
| 1645 | all_is_const = false; |
| 1646 | } |
| 1647 | } |
| 1648 | |
| 1649 | Object *baseptr = base.value; |
| 1650 | |
| 1651 | if (all_is_const && call->function_name == SNAME("get_node" ) && ClassDB::is_parent_class(native_type.native_type, SNAME("Node" )) && args.size()) { |
| 1652 | String arg1 = args[0]; |
| 1653 | if (arg1.begins_with("/root/" )) { |
| 1654 | String which = arg1.get_slice("/" , 2); |
| 1655 | if (!which.is_empty()) { |
| 1656 | // Try singletons first |
| 1657 | if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(which)) { |
| 1658 | r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which]); |
| 1659 | found = true; |
| 1660 | } else { |
| 1661 | for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) { |
| 1662 | String name = E.key; |
| 1663 | if (name == which) { |
| 1664 | String script = E.value.path; |
| 1665 | |
| 1666 | if (!script.begins_with("res://" )) { |
| 1667 | script = "res://" + script; |
| 1668 | } |
| 1669 | |
| 1670 | if (!script.ends_with(".gd" )) { |
| 1671 | // not a script, try find the script anyway, |
| 1672 | // may have some success |
| 1673 | script = script.get_basename() + ".gd" ; |
| 1674 | } |
| 1675 | |
| 1676 | if (FileAccess::exists(script)) { |
| 1677 | Error err = OK; |
| 1678 | Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(script, GDScriptParserRef::INTERFACE_SOLVED, err); |
| 1679 | if (err == OK) { |
| 1680 | r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; |
| 1681 | r_type.type.script_path = script; |
| 1682 | r_type.type.class_type = parser->get_parser()->get_tree(); |
| 1683 | r_type.type.is_constant = false; |
| 1684 | r_type.type.kind = GDScriptParser::DataType::CLASS; |
| 1685 | r_type.value = Variant(); |
| 1686 | p_context.dependent_parsers.push_back(parser); |
| 1687 | found = true; |
| 1688 | } |
| 1689 | } |
| 1690 | break; |
| 1691 | } |
| 1692 | } |
| 1693 | } |
| 1694 | } |
| 1695 | } |
| 1696 | } |
| 1697 | |
| 1698 | if (!found && all_is_const && baseptr) { |
| 1699 | Vector<const Variant *> argptr; |
| 1700 | for (int i = 0; i < args.size(); i++) { |
| 1701 | argptr.push_back(&args[i]); |
| 1702 | } |
| 1703 | |
| 1704 | Callable::CallError ce; |
| 1705 | Variant ret = mb->call(baseptr, (const Variant **)argptr.ptr(), argptr.size(), ce); |
| 1706 | |
| 1707 | if (ce.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) { |
| 1708 | if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != nullptr) { |
| 1709 | r_type = _type_from_variant(ret); |
| 1710 | found = true; |
| 1711 | } |
| 1712 | } |
| 1713 | } |
| 1714 | } |
| 1715 | } |
| 1716 | } |
| 1717 | |
| 1718 | if (!found && base.value.get_type() != Variant::NIL) { |
| 1719 | found = _guess_method_return_type_from_base(c, base, call->function_name, r_type); |
| 1720 | } |
| 1721 | } |
| 1722 | } break; |
| 1723 | case GDScriptParser::Node::SUBSCRIPT: { |
| 1724 | const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_expression); |
| 1725 | if (subscript->is_attribute) { |
| 1726 | GDScriptParser::CompletionContext c = p_context; |
| 1727 | c.current_line = subscript->start_line; |
| 1728 | |
| 1729 | GDScriptCompletionIdentifier base; |
| 1730 | if (!_guess_expression_type(c, subscript->base, base)) { |
| 1731 | found = false; |
| 1732 | break; |
| 1733 | } |
| 1734 | |
| 1735 | if (base.value.get_type() == Variant::DICTIONARY && base.value.operator Dictionary().has(String(subscript->attribute->name))) { |
| 1736 | Variant value = base.value.operator Dictionary()[String(subscript->attribute->name)]; |
| 1737 | r_type = _type_from_variant(value); |
| 1738 | found = true; |
| 1739 | break; |
| 1740 | } |
| 1741 | |
| 1742 | const GDScriptParser::DictionaryNode *dn = nullptr; |
| 1743 | if (subscript->base->type == GDScriptParser::Node::DICTIONARY) { |
| 1744 | dn = static_cast<const GDScriptParser::DictionaryNode *>(subscript->base); |
| 1745 | } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::DICTIONARY) { |
| 1746 | dn = static_cast<const GDScriptParser::DictionaryNode *>(base.assigned_expression); |
| 1747 | } |
| 1748 | |
| 1749 | if (dn) { |
| 1750 | for (int i = 0; i < dn->elements.size(); i++) { |
| 1751 | GDScriptCompletionIdentifier key; |
| 1752 | if (!_guess_expression_type(c, dn->elements[i].key, key)) { |
| 1753 | continue; |
| 1754 | } |
| 1755 | if (key.value == String(subscript->attribute->name)) { |
| 1756 | r_type.assigned_expression = dn->elements[i].value; |
| 1757 | found = _guess_expression_type(c, dn->elements[i].value, r_type); |
| 1758 | break; |
| 1759 | } |
| 1760 | } |
| 1761 | } |
| 1762 | |
| 1763 | if (!found) { |
| 1764 | found = _guess_identifier_type_from_base(c, base, subscript->attribute->name, r_type); |
| 1765 | } |
| 1766 | } else { |
| 1767 | if (subscript->index == nullptr) { |
| 1768 | found = false; |
| 1769 | break; |
| 1770 | } |
| 1771 | |
| 1772 | GDScriptParser::CompletionContext c = p_context; |
| 1773 | c.current_line = subscript->start_line; |
| 1774 | |
| 1775 | GDScriptCompletionIdentifier base; |
| 1776 | if (!_guess_expression_type(c, subscript->base, base)) { |
| 1777 | found = false; |
| 1778 | break; |
| 1779 | } |
| 1780 | |
| 1781 | GDScriptCompletionIdentifier index; |
| 1782 | if (!_guess_expression_type(c, subscript->index, index)) { |
| 1783 | found = false; |
| 1784 | break; |
| 1785 | } |
| 1786 | |
| 1787 | if (base.value.in(index.value)) { |
| 1788 | Variant value = base.value.get(index.value); |
| 1789 | r_type = _type_from_variant(value); |
| 1790 | found = true; |
| 1791 | break; |
| 1792 | } |
| 1793 | |
| 1794 | // Look if it is a dictionary node. |
| 1795 | const GDScriptParser::DictionaryNode *dn = nullptr; |
| 1796 | if (subscript->base->type == GDScriptParser::Node::DICTIONARY) { |
| 1797 | dn = static_cast<const GDScriptParser::DictionaryNode *>(subscript->base); |
| 1798 | } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::DICTIONARY) { |
| 1799 | dn = static_cast<const GDScriptParser::DictionaryNode *>(base.assigned_expression); |
| 1800 | } |
| 1801 | |
| 1802 | if (dn) { |
| 1803 | for (int i = 0; i < dn->elements.size(); i++) { |
| 1804 | GDScriptCompletionIdentifier key; |
| 1805 | if (!_guess_expression_type(c, dn->elements[i].key, key)) { |
| 1806 | continue; |
| 1807 | } |
| 1808 | if (key.value == index.value) { |
| 1809 | r_type.assigned_expression = dn->elements[i].value; |
| 1810 | found = _guess_expression_type(p_context, dn->elements[i].value, r_type); |
| 1811 | break; |
| 1812 | } |
| 1813 | } |
| 1814 | } |
| 1815 | |
| 1816 | // Look if it is an array node. |
| 1817 | if (!found && index.value.is_num()) { |
| 1818 | int idx = index.value; |
| 1819 | const GDScriptParser::ArrayNode *an = nullptr; |
| 1820 | if (subscript->base->type == GDScriptParser::Node::ARRAY) { |
| 1821 | an = static_cast<const GDScriptParser::ArrayNode *>(subscript->base); |
| 1822 | } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::ARRAY) { |
| 1823 | an = static_cast<const GDScriptParser::ArrayNode *>(base.assigned_expression); |
| 1824 | } |
| 1825 | |
| 1826 | if (an && idx >= 0 && an->elements.size() > idx) { |
| 1827 | r_type.assigned_expression = an->elements[idx]; |
| 1828 | found = _guess_expression_type(c, an->elements[idx], r_type); |
| 1829 | break; |
| 1830 | } |
| 1831 | } |
| 1832 | |
| 1833 | // Look for valid indexing in other types |
| 1834 | if (!found && (index.value.get_type() == Variant::STRING || index.value.get_type() == Variant::NODE_PATH)) { |
| 1835 | StringName id = index.value; |
| 1836 | found = _guess_identifier_type_from_base(c, base, id, r_type); |
| 1837 | } else if (!found && index.type.kind == GDScriptParser::DataType::BUILTIN) { |
| 1838 | Callable::CallError err; |
| 1839 | Variant base_val; |
| 1840 | Variant::construct(base.type.builtin_type, base_val, nullptr, 0, err); |
| 1841 | bool valid = false; |
| 1842 | Variant res = base_val.get(index.value, &valid); |
| 1843 | if (valid) { |
| 1844 | r_type = _type_from_variant(res); |
| 1845 | r_type.value = Variant(); |
| 1846 | r_type.type.is_constant = false; |
| 1847 | found = true; |
| 1848 | } |
| 1849 | } |
| 1850 | } |
| 1851 | } break; |
| 1852 | case GDScriptParser::Node::BINARY_OPERATOR: { |
| 1853 | const GDScriptParser::BinaryOpNode *op = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression); |
| 1854 | |
| 1855 | if (op->variant_op == Variant::OP_MAX) { |
| 1856 | break; |
| 1857 | } |
| 1858 | |
| 1859 | GDScriptParser::CompletionContext context = p_context; |
| 1860 | context.current_line = op->start_line; |
| 1861 | |
| 1862 | GDScriptCompletionIdentifier p1; |
| 1863 | GDScriptCompletionIdentifier p2; |
| 1864 | |
| 1865 | if (!_guess_expression_type(context, op->left_operand, p1)) { |
| 1866 | found = false; |
| 1867 | break; |
| 1868 | } |
| 1869 | |
| 1870 | if (!_guess_expression_type(context, op->right_operand, p2)) { |
| 1871 | found = false; |
| 1872 | break; |
| 1873 | } |
| 1874 | |
| 1875 | Callable::CallError ce; |
| 1876 | bool v1_use_value = p1.value.get_type() != Variant::NIL && p1.value.get_type() != Variant::OBJECT; |
| 1877 | Variant d1; |
| 1878 | Variant::construct(p1.type.builtin_type, d1, nullptr, 0, ce); |
| 1879 | Variant d2; |
| 1880 | Variant::construct(p2.type.builtin_type, d2, nullptr, 0, ce); |
| 1881 | |
| 1882 | Variant v1 = (v1_use_value) ? p1.value : d1; |
| 1883 | bool v2_use_value = p2.value.get_type() != Variant::NIL && p2.value.get_type() != Variant::OBJECT; |
| 1884 | Variant v2 = (v2_use_value) ? p2.value : d2; |
| 1885 | // avoid potential invalid ops |
| 1886 | if ((op->variant_op == Variant::OP_DIVIDE || op->variant_op == Variant::OP_MODULE) && v2.get_type() == Variant::INT) { |
| 1887 | v2 = 1; |
| 1888 | v2_use_value = false; |
| 1889 | } |
| 1890 | if (op->variant_op == Variant::OP_DIVIDE && v2.get_type() == Variant::FLOAT) { |
| 1891 | v2 = 1.0; |
| 1892 | v2_use_value = false; |
| 1893 | } |
| 1894 | |
| 1895 | Variant res; |
| 1896 | bool valid; |
| 1897 | Variant::evaluate(op->variant_op, v1, v2, res, valid); |
| 1898 | if (!valid) { |
| 1899 | found = false; |
| 1900 | break; |
| 1901 | } |
| 1902 | r_type = _type_from_variant(res); |
| 1903 | if (!v1_use_value || !v2_use_value) { |
| 1904 | r_type.value = Variant(); |
| 1905 | r_type.type.is_constant = false; |
| 1906 | } |
| 1907 | |
| 1908 | found = true; |
| 1909 | } break; |
| 1910 | default: |
| 1911 | break; |
| 1912 | } |
| 1913 | } |
| 1914 | |
| 1915 | // It may have found a null, but that's never useful |
| 1916 | if (found && r_type.type.kind == GDScriptParser::DataType::BUILTIN && r_type.type.builtin_type == Variant::NIL) { |
| 1917 | found = false; |
| 1918 | } |
| 1919 | |
| 1920 | // Check type hint last. For collections we want chance to get the actual value first |
| 1921 | // This way we can detect types from the content of dictionaries and arrays |
| 1922 | if (!found && p_expression->get_datatype().is_hard_type()) { |
| 1923 | r_type.type = p_expression->get_datatype(); |
| 1924 | if (!r_type.assigned_expression) { |
| 1925 | r_type.assigned_expression = p_expression; |
| 1926 | } |
| 1927 | found = true; |
| 1928 | } |
| 1929 | |
| 1930 | return found; |
| 1931 | } |
| 1932 | |
| 1933 | static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::IdentifierNode *p_identifier, GDScriptCompletionIdentifier &r_type) { |
| 1934 | static int recursion_depth = 0; |
| 1935 | RecursionCheck recursion(&recursion_depth); |
| 1936 | if (unlikely(recursion.check())) { |
| 1937 | ERR_FAIL_V_MSG(false, "Reached recursion limit while trying to guess type." ); |
| 1938 | } |
| 1939 | |
| 1940 | // Look in blocks first. |
| 1941 | int last_assign_line = -1; |
| 1942 | const GDScriptParser::ExpressionNode *last_assigned_expression = nullptr; |
| 1943 | GDScriptParser::DataType id_type; |
| 1944 | GDScriptParser::SuiteNode *suite = p_context.current_suite; |
| 1945 | bool is_function_parameter = false; |
| 1946 | |
| 1947 | bool can_be_local = true; |
| 1948 | switch (p_identifier->source) { |
| 1949 | case GDScriptParser::IdentifierNode::MEMBER_VARIABLE: |
| 1950 | case GDScriptParser::IdentifierNode::MEMBER_CONSTANT: |
| 1951 | case GDScriptParser::IdentifierNode::MEMBER_FUNCTION: |
| 1952 | case GDScriptParser::IdentifierNode::MEMBER_SIGNAL: |
| 1953 | case GDScriptParser::IdentifierNode::MEMBER_CLASS: |
| 1954 | case GDScriptParser::IdentifierNode::INHERITED_VARIABLE: |
| 1955 | case GDScriptParser::IdentifierNode::STATIC_VARIABLE: |
| 1956 | can_be_local = false; |
| 1957 | break; |
| 1958 | default: |
| 1959 | break; |
| 1960 | } |
| 1961 | |
| 1962 | if (can_be_local && suite && suite->has_local(p_identifier->name)) { |
| 1963 | const GDScriptParser::SuiteNode::Local &local = suite->get_local(p_identifier->name); |
| 1964 | |
| 1965 | id_type = local.get_datatype(); |
| 1966 | |
| 1967 | // Check initializer as the first assignment. |
| 1968 | switch (local.type) { |
| 1969 | case GDScriptParser::SuiteNode::Local::VARIABLE: |
| 1970 | if (local.variable->initializer) { |
| 1971 | last_assign_line = local.variable->initializer->end_line; |
| 1972 | last_assigned_expression = local.variable->initializer; |
| 1973 | } |
| 1974 | break; |
| 1975 | case GDScriptParser::SuiteNode::Local::CONSTANT: |
| 1976 | if (local.constant->initializer) { |
| 1977 | last_assign_line = local.constant->initializer->end_line; |
| 1978 | last_assigned_expression = local.constant->initializer; |
| 1979 | } |
| 1980 | break; |
| 1981 | case GDScriptParser::SuiteNode::Local::PARAMETER: |
| 1982 | if (local.parameter->initializer) { |
| 1983 | last_assign_line = local.parameter->initializer->end_line; |
| 1984 | last_assigned_expression = local.parameter->initializer; |
| 1985 | } |
| 1986 | is_function_parameter = true; |
| 1987 | break; |
| 1988 | default: |
| 1989 | break; |
| 1990 | } |
| 1991 | } |
| 1992 | |
| 1993 | while (suite) { |
| 1994 | for (int i = 0; i < suite->statements.size(); i++) { |
| 1995 | if (suite->statements[i]->end_line >= p_context.current_line) { |
| 1996 | break; |
| 1997 | } |
| 1998 | |
| 1999 | switch (suite->statements[i]->type) { |
| 2000 | case GDScriptParser::Node::ASSIGNMENT: { |
| 2001 | const GDScriptParser::AssignmentNode *assign = static_cast<const GDScriptParser::AssignmentNode *>(suite->statements[i]); |
| 2002 | if (assign->end_line > last_assign_line && assign->assignee && assign->assigned_value && assign->assignee->type == GDScriptParser::Node::IDENTIFIER) { |
| 2003 | const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(assign->assignee); |
| 2004 | if (id->name == p_identifier->name && id->source == p_identifier->source) { |
| 2005 | last_assign_line = assign->assigned_value->end_line; |
| 2006 | last_assigned_expression = assign->assigned_value; |
| 2007 | } |
| 2008 | } |
| 2009 | } break; |
| 2010 | default: |
| 2011 | // TODO: Check sub blocks (control flow statements) as they might also reassign stuff. |
| 2012 | break; |
| 2013 | } |
| 2014 | } |
| 2015 | |
| 2016 | if (suite->parent_if && suite->parent_if->condition && suite->parent_if->condition->type == GDScriptParser::Node::TYPE_TEST) { |
| 2017 | // Operator `is` used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common.. |
| 2018 | // Super dirty hack, but very useful. |
| 2019 | // Credit: Zylann. |
| 2020 | // TODO: this could be hacked to detect ANDed conditions too... |
| 2021 | const GDScriptParser::TypeTestNode *type_test = static_cast<const GDScriptParser::TypeTestNode *>(suite->parent_if->condition); |
| 2022 | if (type_test->operand && type_test->test_type && type_test->operand->type == GDScriptParser::Node::IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(type_test->operand)->name == p_identifier->name && static_cast<const GDScriptParser::IdentifierNode *>(type_test->operand)->source == p_identifier->source) { |
| 2023 | // Bingo. |
| 2024 | GDScriptParser::CompletionContext c = p_context; |
| 2025 | c.current_line = type_test->operand->start_line; |
| 2026 | c.current_suite = suite; |
| 2027 | if (type_test->test_datatype.is_hard_type()) { |
| 2028 | id_type = type_test->test_datatype; |
| 2029 | if (last_assign_line < c.current_line) { |
| 2030 | // Override last assignment. |
| 2031 | last_assign_line = c.current_line; |
| 2032 | last_assigned_expression = nullptr; |
| 2033 | } |
| 2034 | } |
| 2035 | } |
| 2036 | } |
| 2037 | |
| 2038 | suite = suite->parent_block; |
| 2039 | } |
| 2040 | |
| 2041 | if (last_assigned_expression && last_assign_line < p_context.current_line) { |
| 2042 | GDScriptParser::CompletionContext c = p_context; |
| 2043 | c.current_line = last_assign_line; |
| 2044 | r_type.assigned_expression = last_assigned_expression; |
| 2045 | if (_guess_expression_type(c, last_assigned_expression, r_type)) { |
| 2046 | return true; |
| 2047 | } |
| 2048 | } |
| 2049 | |
| 2050 | if (is_function_parameter && p_context.current_function && p_context.current_function->source_lambda == nullptr && p_context.current_class) { |
| 2051 | // Check if it's override of native function, then we can assume the type from the signature. |
| 2052 | GDScriptParser::DataType base_type = p_context.current_class->base_type; |
| 2053 | while (base_type.is_set()) { |
| 2054 | switch (base_type.kind) { |
| 2055 | case GDScriptParser::DataType::CLASS: |
| 2056 | if (base_type.class_type->has_function(p_context.current_function->identifier->name)) { |
| 2057 | GDScriptParser::FunctionNode *parent_function = base_type.class_type->get_member(p_context.current_function->identifier->name).function; |
| 2058 | if (parent_function->parameters_indices.has(p_identifier->name)) { |
| 2059 | const GDScriptParser::ParameterNode *parameter = parent_function->parameters[parent_function->parameters_indices[p_identifier->name]]; |
| 2060 | if ((!id_type.is_set() || id_type.is_variant()) && parameter->get_datatype().is_hard_type()) { |
| 2061 | id_type = parameter->get_datatype(); |
| 2062 | } |
| 2063 | if (parameter->initializer) { |
| 2064 | GDScriptParser::CompletionContext c = p_context; |
| 2065 | c.current_function = parent_function; |
| 2066 | c.current_class = base_type.class_type; |
| 2067 | c.base = nullptr; |
| 2068 | if (_guess_expression_type(c, parameter->initializer, r_type)) { |
| 2069 | return true; |
| 2070 | } |
| 2071 | } |
| 2072 | } |
| 2073 | } |
| 2074 | base_type = base_type.class_type->base_type; |
| 2075 | break; |
| 2076 | case GDScriptParser::DataType::NATIVE: { |
| 2077 | if (id_type.is_set() && !id_type.is_variant()) { |
| 2078 | base_type = GDScriptParser::DataType(); |
| 2079 | break; |
| 2080 | } |
| 2081 | MethodInfo info; |
| 2082 | if (ClassDB::get_method_info(base_type.native_type, p_context.current_function->identifier->name, &info)) { |
| 2083 | for (const PropertyInfo &E : info.arguments) { |
| 2084 | if (E.name == p_identifier->name) { |
| 2085 | r_type = _type_from_property(E); |
| 2086 | return true; |
| 2087 | } |
| 2088 | } |
| 2089 | } |
| 2090 | base_type = GDScriptParser::DataType(); |
| 2091 | } break; |
| 2092 | default: |
| 2093 | break; |
| 2094 | } |
| 2095 | } |
| 2096 | } |
| 2097 | |
| 2098 | if (id_type.is_set() && !id_type.is_variant()) { |
| 2099 | r_type.type = id_type; |
| 2100 | return true; |
| 2101 | } |
| 2102 | |
| 2103 | // Check current class (including inheritance). |
| 2104 | if (p_context.current_class) { |
| 2105 | GDScriptCompletionIdentifier base; |
| 2106 | base.value = p_context.base; |
| 2107 | base.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; |
| 2108 | base.type.kind = GDScriptParser::DataType::CLASS; |
| 2109 | base.type.class_type = p_context.current_class; |
| 2110 | base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static; |
| 2111 | |
| 2112 | if (_guess_identifier_type_from_base(p_context, base, p_identifier->name, r_type)) { |
| 2113 | return true; |
| 2114 | } |
| 2115 | } |
| 2116 | |
| 2117 | // Check global scripts. |
| 2118 | if (ScriptServer::is_global_class(p_identifier->name)) { |
| 2119 | String script = ScriptServer::get_global_class_path(p_identifier->name); |
| 2120 | if (script.to_lower().ends_with(".gd" )) { |
| 2121 | Error err = OK; |
| 2122 | Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(script, GDScriptParserRef::INTERFACE_SOLVED, err); |
| 2123 | if (err == OK) { |
| 2124 | r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; |
| 2125 | r_type.type.script_path = script; |
| 2126 | r_type.type.class_type = parser->get_parser()->get_tree(); |
| 2127 | r_type.type.is_meta_type = true; |
| 2128 | r_type.type.is_constant = false; |
| 2129 | r_type.type.kind = GDScriptParser::DataType::CLASS; |
| 2130 | r_type.value = Variant(); |
| 2131 | p_context.dependent_parsers.push_back(parser); |
| 2132 | return true; |
| 2133 | } |
| 2134 | } else { |
| 2135 | Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier->name)); |
| 2136 | if (scr.is_valid()) { |
| 2137 | r_type = _type_from_variant(scr); |
| 2138 | r_type.type.is_meta_type = true; |
| 2139 | return true; |
| 2140 | } |
| 2141 | } |
| 2142 | return false; |
| 2143 | } |
| 2144 | |
| 2145 | // Check global variables (including autoloads). |
| 2146 | if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier->name)) { |
| 2147 | r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier->name]); |
| 2148 | return true; |
| 2149 | } |
| 2150 | |
| 2151 | // Check ClassDB. |
| 2152 | if (ClassDB::class_exists(p_identifier->name) && ClassDB::is_class_exposed(p_identifier->name)) { |
| 2153 | r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; |
| 2154 | r_type.type.kind = GDScriptParser::DataType::NATIVE; |
| 2155 | r_type.type.native_type = p_identifier->name; |
| 2156 | r_type.type.is_constant = true; |
| 2157 | if (Engine::get_singleton()->has_singleton(p_identifier->name)) { |
| 2158 | r_type.type.is_meta_type = false; |
| 2159 | r_type.value = Engine::get_singleton()->get_singleton_object(p_identifier->name); |
| 2160 | } else { |
| 2161 | r_type.type.is_meta_type = true; |
| 2162 | r_type.value = Variant(); |
| 2163 | } |
| 2164 | } |
| 2165 | |
| 2166 | return false; |
| 2167 | } |
| 2168 | |
| 2169 | static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) { |
| 2170 | static int recursion_depth = 0; |
| 2171 | RecursionCheck recursion(&recursion_depth); |
| 2172 | if (unlikely(recursion.check())) { |
| 2173 | ERR_FAIL_V_MSG(false, "Reached recursion limit while trying to guess type." ); |
| 2174 | } |
| 2175 | |
| 2176 | GDScriptParser::DataType base_type = p_base.type; |
| 2177 | bool is_static = base_type.is_meta_type; |
| 2178 | while (base_type.is_set()) { |
| 2179 | switch (base_type.kind) { |
| 2180 | case GDScriptParser::DataType::CLASS: |
| 2181 | if (base_type.class_type->has_member(p_identifier)) { |
| 2182 | const GDScriptParser::ClassNode::Member &member = base_type.class_type->get_member(p_identifier); |
| 2183 | switch (member.type) { |
| 2184 | case GDScriptParser::ClassNode::Member::CONSTANT: |
| 2185 | r_type.type = member.constant->get_datatype(); |
| 2186 | if (member.constant->initializer && member.constant->initializer->is_constant) { |
| 2187 | r_type.value = member.constant->initializer->reduced_value; |
| 2188 | } |
| 2189 | return true; |
| 2190 | case GDScriptParser::ClassNode::Member::VARIABLE: |
| 2191 | if (!is_static) { |
| 2192 | if (member.variable->get_datatype().is_set() && !member.variable->get_datatype().is_variant()) { |
| 2193 | r_type.type = member.variable->get_datatype(); |
| 2194 | return true; |
| 2195 | } else if (member.variable->initializer) { |
| 2196 | const GDScriptParser::ExpressionNode *init = member.variable->initializer; |
| 2197 | if (init->is_constant) { |
| 2198 | r_type.value = init->reduced_value; |
| 2199 | r_type = _type_from_variant(init->reduced_value); |
| 2200 | return true; |
| 2201 | } else if (init->start_line == p_context.current_line) { |
| 2202 | return false; |
| 2203 | // Detects if variable is assigned to itself |
| 2204 | } else if (_is_expression_named_identifier(init, member.variable->identifier->name)) { |
| 2205 | if (member.variable->initializer->get_datatype().is_set()) { |
| 2206 | r_type.type = member.variable->initializer->get_datatype(); |
| 2207 | } else if (member.variable->get_datatype().is_set() && !member.variable->get_datatype().is_variant()) { |
| 2208 | r_type.type = member.variable->get_datatype(); |
| 2209 | } |
| 2210 | return true; |
| 2211 | } else if (_guess_expression_type(p_context, init, r_type)) { |
| 2212 | return true; |
| 2213 | } else if (init->get_datatype().is_set() && !init->get_datatype().is_variant()) { |
| 2214 | r_type.type = init->get_datatype(); |
| 2215 | return true; |
| 2216 | } |
| 2217 | } |
| 2218 | } |
| 2219 | // TODO: Check assignments in constructor. |
| 2220 | return false; |
| 2221 | case GDScriptParser::ClassNode::Member::ENUM: |
| 2222 | r_type.type = member.m_enum->get_datatype(); |
| 2223 | r_type.enumeration = member.m_enum->identifier->name; |
| 2224 | return true; |
| 2225 | case GDScriptParser::ClassNode::Member::ENUM_VALUE: |
| 2226 | r_type = _type_from_variant(member.enum_value.value); |
| 2227 | return true; |
| 2228 | case GDScriptParser::ClassNode::Member::SIGNAL: |
| 2229 | r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; |
| 2230 | r_type.type.kind = GDScriptParser::DataType::BUILTIN; |
| 2231 | r_type.type.builtin_type = Variant::SIGNAL; |
| 2232 | return true; |
| 2233 | case GDScriptParser::ClassNode::Member::FUNCTION: |
| 2234 | if (is_static && !member.function->is_static) { |
| 2235 | return false; |
| 2236 | } |
| 2237 | r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; |
| 2238 | r_type.type.kind = GDScriptParser::DataType::BUILTIN; |
| 2239 | r_type.type.builtin_type = Variant::CALLABLE; |
| 2240 | return true; |
| 2241 | case GDScriptParser::ClassNode::Member::CLASS: |
| 2242 | r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; |
| 2243 | r_type.type.kind = GDScriptParser::DataType::CLASS; |
| 2244 | r_type.type.class_type = member.m_class; |
| 2245 | r_type.type.is_meta_type = true; |
| 2246 | return true; |
| 2247 | case GDScriptParser::ClassNode::Member::GROUP: |
| 2248 | return false; // No-op, but silences warnings. |
| 2249 | case GDScriptParser::ClassNode::Member::UNDEFINED: |
| 2250 | return false; // Unreachable. |
| 2251 | } |
| 2252 | return false; |
| 2253 | } |
| 2254 | base_type = base_type.class_type->base_type; |
| 2255 | break; |
| 2256 | case GDScriptParser::DataType::SCRIPT: { |
| 2257 | Ref<Script> scr = base_type.script_type; |
| 2258 | if (scr.is_valid()) { |
| 2259 | HashMap<StringName, Variant> constants; |
| 2260 | scr->get_constants(&constants); |
| 2261 | if (constants.has(p_identifier)) { |
| 2262 | r_type = _type_from_variant(constants[p_identifier]); |
| 2263 | return true; |
| 2264 | } |
| 2265 | |
| 2266 | if (!is_static) { |
| 2267 | List<PropertyInfo> members; |
| 2268 | scr->get_script_property_list(&members); |
| 2269 | for (const PropertyInfo &prop : members) { |
| 2270 | if (prop.name == p_identifier) { |
| 2271 | r_type = _type_from_property(prop); |
| 2272 | return true; |
| 2273 | } |
| 2274 | } |
| 2275 | } |
| 2276 | Ref<Script> parent = scr->get_base_script(); |
| 2277 | if (parent.is_valid()) { |
| 2278 | base_type.script_type = parent; |
| 2279 | } else { |
| 2280 | base_type.kind = GDScriptParser::DataType::NATIVE; |
| 2281 | base_type.native_type = scr->get_instance_base_type(); |
| 2282 | } |
| 2283 | } else { |
| 2284 | return false; |
| 2285 | } |
| 2286 | } break; |
| 2287 | case GDScriptParser::DataType::NATIVE: { |
| 2288 | StringName class_name = base_type.native_type; |
| 2289 | if (!ClassDB::class_exists(class_name)) { |
| 2290 | return false; |
| 2291 | } |
| 2292 | |
| 2293 | // Skip constants since they're all integers. Type does not matter because int has no members. |
| 2294 | |
| 2295 | PropertyInfo prop; |
| 2296 | if (ClassDB::get_property_info(class_name, p_identifier, &prop)) { |
| 2297 | StringName getter = ClassDB::get_property_getter(class_name, p_identifier); |
| 2298 | if (getter != StringName()) { |
| 2299 | MethodBind *g = ClassDB::get_method(class_name, getter); |
| 2300 | if (g) { |
| 2301 | r_type = _type_from_property(g->get_return_info()); |
| 2302 | return true; |
| 2303 | } |
| 2304 | } else { |
| 2305 | r_type = _type_from_property(prop); |
| 2306 | return true; |
| 2307 | } |
| 2308 | } |
| 2309 | return false; |
| 2310 | } break; |
| 2311 | case GDScriptParser::DataType::BUILTIN: { |
| 2312 | Callable::CallError err; |
| 2313 | Variant tmp; |
| 2314 | Variant::construct(base_type.builtin_type, tmp, nullptr, 0, err); |
| 2315 | |
| 2316 | if (err.error != Callable::CallError::CALL_OK) { |
| 2317 | return false; |
| 2318 | } |
| 2319 | bool valid = false; |
| 2320 | Variant res = tmp.get(p_identifier, &valid); |
| 2321 | if (valid) { |
| 2322 | r_type = _type_from_variant(res); |
| 2323 | r_type.value = Variant(); |
| 2324 | r_type.type.is_constant = false; |
| 2325 | return true; |
| 2326 | } |
| 2327 | return false; |
| 2328 | } break; |
| 2329 | default: { |
| 2330 | return false; |
| 2331 | } break; |
| 2332 | } |
| 2333 | } |
| 2334 | return false; |
| 2335 | } |
| 2336 | |
| 2337 | static void _find_last_return_in_block(GDScriptParser::CompletionContext &p_context, int &r_last_return_line, const GDScriptParser::ExpressionNode **r_last_returned_value) { |
| 2338 | if (!p_context.current_suite) { |
| 2339 | return; |
| 2340 | } |
| 2341 | |
| 2342 | for (int i = 0; i < p_context.current_suite->statements.size(); i++) { |
| 2343 | if (p_context.current_suite->statements[i]->start_line < r_last_return_line) { |
| 2344 | break; |
| 2345 | } |
| 2346 | |
| 2347 | GDScriptParser::CompletionContext c = p_context; |
| 2348 | switch (p_context.current_suite->statements[i]->type) { |
| 2349 | case GDScriptParser::Node::FOR: |
| 2350 | c.current_suite = static_cast<const GDScriptParser::ForNode *>(p_context.current_suite->statements[i])->loop; |
| 2351 | _find_last_return_in_block(c, r_last_return_line, r_last_returned_value); |
| 2352 | break; |
| 2353 | case GDScriptParser::Node::WHILE: |
| 2354 | c.current_suite = static_cast<const GDScriptParser::WhileNode *>(p_context.current_suite->statements[i])->loop; |
| 2355 | _find_last_return_in_block(c, r_last_return_line, r_last_returned_value); |
| 2356 | break; |
| 2357 | case GDScriptParser::Node::IF: { |
| 2358 | const GDScriptParser::IfNode *_if = static_cast<const GDScriptParser::IfNode *>(p_context.current_suite->statements[i]); |
| 2359 | c.current_suite = _if->true_block; |
| 2360 | _find_last_return_in_block(c, r_last_return_line, r_last_returned_value); |
| 2361 | if (_if->false_block) { |
| 2362 | c.current_suite = _if->false_block; |
| 2363 | _find_last_return_in_block(c, r_last_return_line, r_last_returned_value); |
| 2364 | } |
| 2365 | } break; |
| 2366 | case GDScriptParser::Node::MATCH: { |
| 2367 | const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(p_context.current_suite->statements[i]); |
| 2368 | for (int j = 0; j < match->branches.size(); j++) { |
| 2369 | c.current_suite = match->branches[j]->block; |
| 2370 | _find_last_return_in_block(c, r_last_return_line, r_last_returned_value); |
| 2371 | } |
| 2372 | } break; |
| 2373 | case GDScriptParser::Node::RETURN: { |
| 2374 | const GDScriptParser::ReturnNode *ret = static_cast<const GDScriptParser::ReturnNode *>(p_context.current_suite->statements[i]); |
| 2375 | if (ret->return_value) { |
| 2376 | if (ret->start_line > r_last_return_line) { |
| 2377 | r_last_return_line = ret->start_line; |
| 2378 | *r_last_returned_value = ret->return_value; |
| 2379 | } |
| 2380 | } |
| 2381 | } break; |
| 2382 | default: |
| 2383 | break; |
| 2384 | } |
| 2385 | } |
| 2386 | } |
| 2387 | |
| 2388 | static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type) { |
| 2389 | static int recursion_depth = 0; |
| 2390 | RecursionCheck recursion(&recursion_depth); |
| 2391 | if (unlikely(recursion.check())) { |
| 2392 | ERR_FAIL_V_MSG(false, "Reached recursion limit while trying to guess type." ); |
| 2393 | } |
| 2394 | |
| 2395 | GDScriptParser::DataType base_type = p_base.type; |
| 2396 | bool is_static = base_type.is_meta_type; |
| 2397 | |
| 2398 | if (is_static && p_method == SNAME("new" )) { |
| 2399 | r_type.type = base_type; |
| 2400 | r_type.type.is_meta_type = false; |
| 2401 | r_type.type.is_constant = false; |
| 2402 | return true; |
| 2403 | } |
| 2404 | |
| 2405 | while (base_type.is_set() && !base_type.is_variant()) { |
| 2406 | switch (base_type.kind) { |
| 2407 | case GDScriptParser::DataType::CLASS: |
| 2408 | if (base_type.class_type->has_function(p_method)) { |
| 2409 | const GDScriptParser::FunctionNode *method = base_type.class_type->get_member(p_method).function; |
| 2410 | if (!is_static || method->is_static) { |
| 2411 | if (method->get_datatype().is_set() && !method->get_datatype().is_variant()) { |
| 2412 | r_type.type = method->get_datatype(); |
| 2413 | return true; |
| 2414 | } |
| 2415 | |
| 2416 | int last_return_line = -1; |
| 2417 | const GDScriptParser::ExpressionNode *last_returned_value = nullptr; |
| 2418 | GDScriptParser::CompletionContext c = p_context; |
| 2419 | c.current_class = base_type.class_type; |
| 2420 | c.current_function = const_cast<GDScriptParser::FunctionNode *>(method); |
| 2421 | c.current_suite = method->body; |
| 2422 | |
| 2423 | _find_last_return_in_block(c, last_return_line, &last_returned_value); |
| 2424 | if (last_returned_value) { |
| 2425 | c.current_line = c.current_suite->end_line; |
| 2426 | if (_guess_expression_type(c, last_returned_value, r_type)) { |
| 2427 | return true; |
| 2428 | } |
| 2429 | } |
| 2430 | } |
| 2431 | } |
| 2432 | base_type = base_type.class_type->base_type; |
| 2433 | break; |
| 2434 | case GDScriptParser::DataType::SCRIPT: { |
| 2435 | Ref<Script> scr = base_type.script_type; |
| 2436 | if (scr.is_valid()) { |
| 2437 | List<MethodInfo> methods; |
| 2438 | scr->get_script_method_list(&methods); |
| 2439 | for (const MethodInfo &mi : methods) { |
| 2440 | if (mi.name == p_method) { |
| 2441 | r_type = _type_from_property(mi.return_val); |
| 2442 | return true; |
| 2443 | } |
| 2444 | } |
| 2445 | Ref<Script> base_script = scr->get_base_script(); |
| 2446 | if (base_script.is_valid()) { |
| 2447 | base_type.script_type = base_script; |
| 2448 | } else { |
| 2449 | base_type.kind = GDScriptParser::DataType::NATIVE; |
| 2450 | base_type.native_type = scr->get_instance_base_type(); |
| 2451 | } |
| 2452 | } else { |
| 2453 | return false; |
| 2454 | } |
| 2455 | } break; |
| 2456 | case GDScriptParser::DataType::NATIVE: { |
| 2457 | if (!ClassDB::class_exists(base_type.native_type)) { |
| 2458 | return false; |
| 2459 | } |
| 2460 | MethodBind *mb = ClassDB::get_method(base_type.native_type, p_method); |
| 2461 | if (mb) { |
| 2462 | r_type = _type_from_property(mb->get_return_info()); |
| 2463 | return true; |
| 2464 | } |
| 2465 | return false; |
| 2466 | } break; |
| 2467 | case GDScriptParser::DataType::BUILTIN: { |
| 2468 | Callable::CallError err; |
| 2469 | Variant tmp; |
| 2470 | Variant::construct(base_type.builtin_type, tmp, nullptr, 0, err); |
| 2471 | if (err.error != Callable::CallError::CALL_OK) { |
| 2472 | return false; |
| 2473 | } |
| 2474 | |
| 2475 | List<MethodInfo> methods; |
| 2476 | tmp.get_method_list(&methods); |
| 2477 | |
| 2478 | for (const MethodInfo &mi : methods) { |
| 2479 | if (mi.name == p_method) { |
| 2480 | r_type = _type_from_property(mi.return_val); |
| 2481 | return true; |
| 2482 | } |
| 2483 | } |
| 2484 | return false; |
| 2485 | } break; |
| 2486 | default: { |
| 2487 | return false; |
| 2488 | } |
| 2489 | } |
| 2490 | } |
| 2491 | |
| 2492 | return false; |
| 2493 | } |
| 2494 | |
| 2495 | static void _find_enumeration_candidates(GDScriptParser::CompletionContext &p_context, const String &p_enum_hint, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result) { |
| 2496 | if (!p_enum_hint.contains("." )) { |
| 2497 | // Global constant or in the current class. |
| 2498 | StringName current_enum = p_enum_hint; |
| 2499 | 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) { |
| 2500 | const GDScriptParser::EnumNode *_enum = p_context.current_class->get_member(current_enum).m_enum; |
| 2501 | for (int i = 0; i < _enum->values.size(); i++) { |
| 2502 | ScriptLanguage::CodeCompletionOption option(_enum->values[i].identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_ENUM); |
| 2503 | r_result.insert(option.display, option); |
| 2504 | } |
| 2505 | } else { |
| 2506 | for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) { |
| 2507 | if (CoreConstants::get_global_constant_enum(i) == current_enum) { |
| 2508 | ScriptLanguage::CodeCompletionOption option(CoreConstants::get_global_constant_name(i), ScriptLanguage::CODE_COMPLETION_KIND_ENUM); |
| 2509 | r_result.insert(option.display, option); |
| 2510 | } |
| 2511 | } |
| 2512 | } |
| 2513 | } else { |
| 2514 | String class_name = p_enum_hint.get_slice("." , 0); |
| 2515 | String enum_name = p_enum_hint.get_slice("." , 1); |
| 2516 | |
| 2517 | if (!ClassDB::class_exists(class_name)) { |
| 2518 | return; |
| 2519 | } |
| 2520 | |
| 2521 | List<StringName> enum_constants; |
| 2522 | ClassDB::get_enum_constants(class_name, enum_name, &enum_constants); |
| 2523 | for (const StringName &E : enum_constants) { |
| 2524 | String candidate = class_name + "." + E; |
| 2525 | int location = _get_enum_constant_location(class_name, E); |
| 2526 | ScriptLanguage::CodeCompletionOption option(candidate, ScriptLanguage::CODE_COMPLETION_KIND_ENUM, location); |
| 2527 | r_result.insert(option.display, option); |
| 2528 | } |
| 2529 | } |
| 2530 | } |
| 2531 | |
| 2532 | 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) { |
| 2533 | Variant base = p_base.value; |
| 2534 | GDScriptParser::DataType base_type = p_base.type; |
| 2535 | |
| 2536 | const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes" ) ? "'" : "\"" ; |
| 2537 | |
| 2538 | while (base_type.is_set() && !base_type.is_variant()) { |
| 2539 | switch (base_type.kind) { |
| 2540 | case GDScriptParser::DataType::CLASS: { |
| 2541 | if (base_type.class_type->has_member(p_method)) { |
| 2542 | const GDScriptParser::ClassNode::Member &member = base_type.class_type->get_member(p_method); |
| 2543 | |
| 2544 | if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) { |
| 2545 | r_arghint = _make_arguments_hint(member.function, p_argidx); |
| 2546 | return; |
| 2547 | } |
| 2548 | } |
| 2549 | |
| 2550 | base_type = base_type.class_type->base_type; |
| 2551 | } break; |
| 2552 | case GDScriptParser::DataType::NATIVE: { |
| 2553 | StringName class_name = base_type.native_type; |
| 2554 | if (!ClassDB::class_exists(class_name)) { |
| 2555 | base_type.kind = GDScriptParser::DataType::UNRESOLVED; |
| 2556 | break; |
| 2557 | } |
| 2558 | |
| 2559 | MethodInfo info; |
| 2560 | int method_args = 0; |
| 2561 | |
| 2562 | if (ClassDB::get_method_info(class_name, p_method, &info)) { |
| 2563 | method_args = info.arguments.size(); |
| 2564 | if (base.get_type() == Variant::OBJECT) { |
| 2565 | Object *obj = base.operator Object *(); |
| 2566 | if (obj) { |
| 2567 | List<String> options; |
| 2568 | obj->get_argument_options(p_method, p_argidx, &options); |
| 2569 | for (String &opt : options) { |
| 2570 | if (opt.is_quoted()) { |
| 2571 | opt = opt.unquote().quote(quote_style); // Handle user preference. |
| 2572 | } |
| 2573 | ScriptLanguage::CodeCompletionOption option(opt, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); |
| 2574 | r_result.insert(option.display, option); |
| 2575 | } |
| 2576 | } |
| 2577 | } |
| 2578 | |
| 2579 | if (p_argidx < method_args) { |
| 2580 | PropertyInfo arg_info = info.arguments[p_argidx]; |
| 2581 | if (arg_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) { |
| 2582 | _find_enumeration_candidates(p_context, arg_info.class_name, r_result); |
| 2583 | } |
| 2584 | } |
| 2585 | |
| 2586 | r_arghint = _make_arguments_hint(info, p_argidx); |
| 2587 | } |
| 2588 | |
| 2589 | if (p_argidx == 0 && ClassDB::is_parent_class(class_name, SNAME("Node" )) && (p_method == SNAME("get_node" ) || p_method == SNAME("has_node" ))) { |
| 2590 | // Get autoloads |
| 2591 | List<PropertyInfo> props; |
| 2592 | ProjectSettings::get_singleton()->get_property_list(&props); |
| 2593 | |
| 2594 | for (const PropertyInfo &E : props) { |
| 2595 | String s = E.name; |
| 2596 | if (!s.begins_with("autoload/" )) { |
| 2597 | continue; |
| 2598 | } |
| 2599 | String name = s.get_slice("/" , 1); |
| 2600 | ScriptLanguage::CodeCompletionOption option("/root/" + name, ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH); |
| 2601 | option.insert_text = option.display.quote(quote_style); |
| 2602 | r_result.insert(option.display, option); |
| 2603 | } |
| 2604 | } |
| 2605 | |
| 2606 | if (p_argidx == 0 && method_args > 0 && ClassDB::is_parent_class(class_name, SNAME("InputEvent" )) && p_method.operator String().contains("action" )) { |
| 2607 | // Get input actions |
| 2608 | List<PropertyInfo> props; |
| 2609 | ProjectSettings::get_singleton()->get_property_list(&props); |
| 2610 | for (const PropertyInfo &E : props) { |
| 2611 | String s = E.name; |
| 2612 | if (!s.begins_with("input/" )) { |
| 2613 | continue; |
| 2614 | } |
| 2615 | String name = s.get_slice("/" , 1); |
| 2616 | ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT); |
| 2617 | option.insert_text = option.display.quote(quote_style); |
| 2618 | r_result.insert(option.display, option); |
| 2619 | } |
| 2620 | } |
| 2621 | |
| 2622 | base_type.kind = GDScriptParser::DataType::UNRESOLVED; |
| 2623 | } break; |
| 2624 | case GDScriptParser::DataType::BUILTIN: { |
| 2625 | if (base.get_type() == Variant::NIL) { |
| 2626 | Callable::CallError err; |
| 2627 | Variant::construct(base_type.builtin_type, base, nullptr, 0, err); |
| 2628 | if (err.error != Callable::CallError::CALL_OK) { |
| 2629 | return; |
| 2630 | } |
| 2631 | } |
| 2632 | |
| 2633 | List<MethodInfo> methods; |
| 2634 | base.get_method_list(&methods); |
| 2635 | for (const MethodInfo &E : methods) { |
| 2636 | if (E.name == p_method) { |
| 2637 | r_arghint = _make_arguments_hint(E, p_argidx); |
| 2638 | return; |
| 2639 | } |
| 2640 | } |
| 2641 | |
| 2642 | base_type.kind = GDScriptParser::DataType::UNRESOLVED; |
| 2643 | } break; |
| 2644 | default: { |
| 2645 | base_type.kind = GDScriptParser::DataType::UNRESOLVED; |
| 2646 | } break; |
| 2647 | } |
| 2648 | } |
| 2649 | } |
| 2650 | |
| 2651 | static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::SubscriptNode *p_subscript, GDScriptParser::DataType &r_base_type, Variant *r_base = nullptr) { |
| 2652 | if (p_context.base == nullptr) { |
| 2653 | return false; |
| 2654 | } |
| 2655 | const GDScriptParser::GetNodeNode *get_node = nullptr; |
| 2656 | |
| 2657 | switch (p_subscript->base->type) { |
| 2658 | case GDScriptParser::Node::GET_NODE: { |
| 2659 | get_node = static_cast<GDScriptParser::GetNodeNode *>(p_subscript->base); |
| 2660 | } break; |
| 2661 | |
| 2662 | case GDScriptParser::Node::IDENTIFIER: { |
| 2663 | const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base); |
| 2664 | |
| 2665 | switch (identifier_node->source) { |
| 2666 | case GDScriptParser::IdentifierNode::Source::MEMBER_VARIABLE: { |
| 2667 | if (p_context.current_class != nullptr) { |
| 2668 | const StringName &member_name = identifier_node->name; |
| 2669 | const GDScriptParser::ClassNode *current_class = p_context.current_class; |
| 2670 | |
| 2671 | if (current_class->has_member(member_name)) { |
| 2672 | const GDScriptParser::ClassNode::Member &member = current_class->get_member(member_name); |
| 2673 | |
| 2674 | if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { |
| 2675 | const GDScriptParser::VariableNode *variable = static_cast<GDScriptParser::VariableNode *>(member.variable); |
| 2676 | |
| 2677 | if (variable->initializer && variable->initializer->type == GDScriptParser::Node::GET_NODE) { |
| 2678 | get_node = static_cast<GDScriptParser::GetNodeNode *>(variable->initializer); |
| 2679 | } |
| 2680 | } |
| 2681 | } |
| 2682 | } |
| 2683 | } break; |
| 2684 | case GDScriptParser::IdentifierNode::Source::LOCAL_VARIABLE: { |
| 2685 | if (identifier_node->next != nullptr && identifier_node->next->type == GDScriptParser::ClassNode::Node::GET_NODE) { |
| 2686 | get_node = static_cast<GDScriptParser::GetNodeNode *>(identifier_node->next); |
| 2687 | } |
| 2688 | } break; |
| 2689 | default: { |
| 2690 | } break; |
| 2691 | } |
| 2692 | } break; |
| 2693 | default: { |
| 2694 | } break; |
| 2695 | } |
| 2696 | |
| 2697 | if (get_node != nullptr) { |
| 2698 | const Object *node = p_context.base->call("get_node_or_null" , NodePath(get_node->full_path)); |
| 2699 | if (node != nullptr) { |
| 2700 | if (r_base != nullptr) { |
| 2701 | *r_base = node; |
| 2702 | } |
| 2703 | r_base_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; |
| 2704 | r_base_type.kind = GDScriptParser::DataType::NATIVE; |
| 2705 | r_base_type.native_type = node->get_class_name(); |
| 2706 | r_base_type.builtin_type = Variant::OBJECT; |
| 2707 | return true; |
| 2708 | } |
| 2709 | } |
| 2710 | |
| 2711 | return false; |
| 2712 | } |
| 2713 | |
| 2714 | 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) { |
| 2715 | if (p_call->type == GDScriptParser::Node::PRELOAD) { |
| 2716 | if (p_argidx == 0 && bool(EDITOR_GET("text_editor/completion/complete_file_paths" ))) { |
| 2717 | _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), r_result); |
| 2718 | } |
| 2719 | |
| 2720 | MethodInfo mi(PropertyInfo(Variant::OBJECT, "resource" , PROPERTY_HINT_RESOURCE_TYPE, "Resource" ), "preload" , PropertyInfo(Variant::STRING, "path" )); |
| 2721 | r_arghint = _make_arguments_hint(mi, p_argidx); |
| 2722 | return; |
| 2723 | } else if (p_call->type != GDScriptParser::Node::CALL) { |
| 2724 | return; |
| 2725 | } |
| 2726 | |
| 2727 | Variant base; |
| 2728 | GDScriptParser::DataType base_type; |
| 2729 | bool _static = false; |
| 2730 | const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_call); |
| 2731 | GDScriptParser::Node::Type callee_type = call->get_callee_type(); |
| 2732 | |
| 2733 | GDScriptCompletionIdentifier connect_base; |
| 2734 | |
| 2735 | if (callee_type == GDScriptParser::Node::SUBSCRIPT) { |
| 2736 | const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(call->callee); |
| 2737 | |
| 2738 | if (subscript->base != nullptr && subscript->base->type == GDScriptParser::Node::IDENTIFIER) { |
| 2739 | const GDScriptParser::IdentifierNode *base_identifier = static_cast<const GDScriptParser::IdentifierNode *>(subscript->base); |
| 2740 | |
| 2741 | Variant::Type method_type = GDScriptParser::get_builtin_type(base_identifier->name); |
| 2742 | if (method_type < Variant::VARIANT_MAX) { |
| 2743 | Variant v; |
| 2744 | Callable::CallError err; |
| 2745 | Variant::construct(method_type, v, nullptr, 0, err); |
| 2746 | if (err.error != Callable::CallError::CALL_OK) { |
| 2747 | return; |
| 2748 | } |
| 2749 | List<MethodInfo> methods; |
| 2750 | v.get_method_list(&methods); |
| 2751 | |
| 2752 | for (MethodInfo &E : methods) { |
| 2753 | if (p_argidx >= E.arguments.size()) { |
| 2754 | continue; |
| 2755 | } |
| 2756 | if (E.name == call->function_name) { |
| 2757 | r_arghint += _make_arguments_hint(E, p_argidx); |
| 2758 | return; |
| 2759 | } |
| 2760 | } |
| 2761 | } |
| 2762 | } |
| 2763 | |
| 2764 | if (subscript->is_attribute) { |
| 2765 | bool found_type = _get_subscript_type(p_context, subscript, base_type, &base); |
| 2766 | |
| 2767 | if (!found_type) { |
| 2768 | GDScriptCompletionIdentifier ci; |
| 2769 | if (_guess_expression_type(p_context, subscript->base, ci)) { |
| 2770 | base_type = ci.type; |
| 2771 | base = ci.value; |
| 2772 | } else { |
| 2773 | return; |
| 2774 | } |
| 2775 | } |
| 2776 | |
| 2777 | _static = base_type.is_meta_type; |
| 2778 | } |
| 2779 | } else if (Variant::has_utility_function(call->function_name)) { |
| 2780 | MethodInfo info = Variant::get_utility_function_info(call->function_name); |
| 2781 | r_arghint = _make_arguments_hint(info, p_argidx); |
| 2782 | return; |
| 2783 | } else if (GDScriptUtilityFunctions::function_exists(call->function_name)) { |
| 2784 | MethodInfo info = GDScriptUtilityFunctions::get_function_info(call->function_name); |
| 2785 | r_arghint = _make_arguments_hint(info, p_argidx); |
| 2786 | return; |
| 2787 | } else if (GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) { |
| 2788 | // Complete constructor. |
| 2789 | List<MethodInfo> constructors; |
| 2790 | Variant::get_constructor_list(GDScriptParser::get_builtin_type(call->function_name), &constructors); |
| 2791 | |
| 2792 | int i = 0; |
| 2793 | for (const MethodInfo &E : constructors) { |
| 2794 | if (p_argidx >= E.arguments.size()) { |
| 2795 | continue; |
| 2796 | } |
| 2797 | if (i > 0) { |
| 2798 | r_arghint += "\n" ; |
| 2799 | } |
| 2800 | r_arghint += _make_arguments_hint(E, p_argidx); |
| 2801 | i++; |
| 2802 | } |
| 2803 | return; |
| 2804 | } else if (call->is_super || callee_type == GDScriptParser::Node::IDENTIFIER) { |
| 2805 | base = p_context.base; |
| 2806 | |
| 2807 | if (p_context.current_class) { |
| 2808 | base_type = p_context.current_class->get_datatype(); |
| 2809 | _static = !p_context.current_function || p_context.current_function->is_static; |
| 2810 | } |
| 2811 | } else { |
| 2812 | return; |
| 2813 | } |
| 2814 | |
| 2815 | GDScriptCompletionIdentifier ci; |
| 2816 | ci.type = base_type; |
| 2817 | ci.value = base; |
| 2818 | _find_call_arguments(p_context, ci, call->function_name, p_argidx, _static, r_result, r_arghint); |
| 2819 | |
| 2820 | r_forced = r_result.size() > 0; |
| 2821 | } |
| 2822 | |
| 2823 | ::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) { |
| 2824 | const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes" ) ? "'" : "\"" ; |
| 2825 | |
| 2826 | GDScriptParser parser; |
| 2827 | GDScriptAnalyzer analyzer(&parser); |
| 2828 | |
| 2829 | parser.parse(p_code, p_path, true); |
| 2830 | analyzer.analyze(); |
| 2831 | |
| 2832 | r_forced = false; |
| 2833 | HashMap<String, ScriptLanguage::CodeCompletionOption> options; |
| 2834 | |
| 2835 | GDScriptParser::CompletionContext completion_context = parser.get_completion_context(); |
| 2836 | completion_context.base = p_owner; |
| 2837 | bool is_function = false; |
| 2838 | |
| 2839 | switch (completion_context.type) { |
| 2840 | case GDScriptParser::COMPLETION_NONE: |
| 2841 | break; |
| 2842 | case GDScriptParser::COMPLETION_ANNOTATION: { |
| 2843 | List<MethodInfo> annotations; |
| 2844 | parser.get_annotation_list(&annotations); |
| 2845 | for (const MethodInfo &E : annotations) { |
| 2846 | ScriptLanguage::CodeCompletionOption option(E.name.substr(1), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); |
| 2847 | if (E.arguments.size() > 0) { |
| 2848 | option.insert_text += "(" ; |
| 2849 | } |
| 2850 | options.insert(option.display, option); |
| 2851 | } |
| 2852 | r_forced = true; |
| 2853 | } break; |
| 2854 | case GDScriptParser::COMPLETION_ANNOTATION_ARGUMENTS: { |
| 2855 | if (completion_context.node == nullptr || completion_context.node->type != GDScriptParser::Node::ANNOTATION) { |
| 2856 | break; |
| 2857 | } |
| 2858 | const GDScriptParser::AnnotationNode *annotation = static_cast<const GDScriptParser::AnnotationNode *>(completion_context.node); |
| 2859 | _find_annotation_arguments(annotation, completion_context.current_argument, quote_style, options); |
| 2860 | r_forced = true; |
| 2861 | } break; |
| 2862 | case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD: { |
| 2863 | // Constants. |
| 2864 | { |
| 2865 | List<StringName> constants; |
| 2866 | Variant::get_constants_for_type(completion_context.builtin_type, &constants); |
| 2867 | for (const StringName &E : constants) { |
| 2868 | ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT); |
| 2869 | bool valid = false; |
| 2870 | Variant default_value = Variant::get_constant_value(completion_context.builtin_type, E, &valid); |
| 2871 | if (valid) { |
| 2872 | option.default_value = default_value; |
| 2873 | } |
| 2874 | options.insert(option.display, option); |
| 2875 | } |
| 2876 | } |
| 2877 | // Methods. |
| 2878 | { |
| 2879 | List<StringName> methods; |
| 2880 | Variant::get_builtin_method_list(completion_context.builtin_type, &methods); |
| 2881 | for (const StringName &E : methods) { |
| 2882 | if (Variant::is_builtin_method_static(completion_context.builtin_type, E)) { |
| 2883 | ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); |
| 2884 | if (Variant::get_builtin_method_argument_count(completion_context.builtin_type, E) > 0 || Variant::is_builtin_method_vararg(completion_context.builtin_type, E)) { |
| 2885 | option.insert_text += "(" ; |
| 2886 | } else { |
| 2887 | option.insert_text += "()" ; |
| 2888 | } |
| 2889 | options.insert(option.display, option); |
| 2890 | } |
| 2891 | } |
| 2892 | } |
| 2893 | } break; |
| 2894 | case GDScriptParser::COMPLETION_INHERIT_TYPE: { |
| 2895 | _list_available_types(true, completion_context, options); |
| 2896 | r_forced = true; |
| 2897 | } break; |
| 2898 | case GDScriptParser::COMPLETION_TYPE_NAME_OR_VOID: { |
| 2899 | ScriptLanguage::CodeCompletionOption option("void" , ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); |
| 2900 | options.insert(option.display, option); |
| 2901 | } |
| 2902 | [[fallthrough]]; |
| 2903 | case GDScriptParser::COMPLETION_TYPE_NAME: { |
| 2904 | _list_available_types(false, completion_context, options); |
| 2905 | r_forced = true; |
| 2906 | } break; |
| 2907 | case GDScriptParser::COMPLETION_PROPERTY_DECLARATION_OR_TYPE: { |
| 2908 | _list_available_types(false, completion_context, options); |
| 2909 | ScriptLanguage::CodeCompletionOption get("get" , ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); |
| 2910 | options.insert(get.display, get); |
| 2911 | ScriptLanguage::CodeCompletionOption set("set" , ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); |
| 2912 | options.insert(set.display, set); |
| 2913 | r_forced = true; |
| 2914 | } break; |
| 2915 | case GDScriptParser::COMPLETION_PROPERTY_DECLARATION: { |
| 2916 | ScriptLanguage::CodeCompletionOption get("get" , ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); |
| 2917 | options.insert(get.display, get); |
| 2918 | ScriptLanguage::CodeCompletionOption set("set" , ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); |
| 2919 | options.insert(set.display, set); |
| 2920 | r_forced = true; |
| 2921 | } break; |
| 2922 | case GDScriptParser::COMPLETION_PROPERTY_METHOD: { |
| 2923 | if (!completion_context.current_class) { |
| 2924 | break; |
| 2925 | } |
| 2926 | for (int i = 0; i < completion_context.current_class->members.size(); i++) { |
| 2927 | const GDScriptParser::ClassNode::Member &member = completion_context.current_class->members[i]; |
| 2928 | if (member.type != GDScriptParser::ClassNode::Member::FUNCTION) { |
| 2929 | continue; |
| 2930 | } |
| 2931 | if (member.function->is_static) { |
| 2932 | continue; |
| 2933 | } |
| 2934 | ScriptLanguage::CodeCompletionOption option(member.function->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); |
| 2935 | options.insert(option.display, option); |
| 2936 | } |
| 2937 | r_forced = true; |
| 2938 | } break; |
| 2939 | case GDScriptParser::COMPLETION_ASSIGN: { |
| 2940 | GDScriptCompletionIdentifier type; |
| 2941 | if (!completion_context.node || completion_context.node->type != GDScriptParser::Node::ASSIGNMENT) { |
| 2942 | break; |
| 2943 | } |
| 2944 | if (!_guess_expression_type(completion_context, static_cast<const GDScriptParser::AssignmentNode *>(completion_context.node)->assignee, type)) { |
| 2945 | _find_identifiers(completion_context, false, options, 0); |
| 2946 | r_forced = true; |
| 2947 | break; |
| 2948 | } |
| 2949 | |
| 2950 | if (!type.enumeration.is_empty()) { |
| 2951 | _find_enumeration_candidates(completion_context, type.enumeration, options); |
| 2952 | r_forced = options.size() > 0; |
| 2953 | } else { |
| 2954 | _find_identifiers(completion_context, false, options, 0); |
| 2955 | r_forced = true; |
| 2956 | } |
| 2957 | } break; |
| 2958 | case GDScriptParser::COMPLETION_METHOD: |
| 2959 | is_function = true; |
| 2960 | [[fallthrough]]; |
| 2961 | case GDScriptParser::COMPLETION_IDENTIFIER: { |
| 2962 | _find_identifiers(completion_context, is_function, options, 0); |
| 2963 | } break; |
| 2964 | case GDScriptParser::COMPLETION_ATTRIBUTE_METHOD: |
| 2965 | is_function = true; |
| 2966 | [[fallthrough]]; |
| 2967 | case GDScriptParser::COMPLETION_ATTRIBUTE: { |
| 2968 | r_forced = true; |
| 2969 | const GDScriptParser::SubscriptNode *attr = static_cast<const GDScriptParser::SubscriptNode *>(completion_context.node); |
| 2970 | if (attr->base) { |
| 2971 | GDScriptCompletionIdentifier base; |
| 2972 | bool found_type = _get_subscript_type(completion_context, attr, base.type); |
| 2973 | if (!found_type && !_guess_expression_type(completion_context, attr->base, base)) { |
| 2974 | break; |
| 2975 | } |
| 2976 | |
| 2977 | _find_identifiers_in_base(base, is_function, options, 0); |
| 2978 | } |
| 2979 | } break; |
| 2980 | case GDScriptParser::COMPLETION_SUBSCRIPT: { |
| 2981 | const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(completion_context.node); |
| 2982 | GDScriptCompletionIdentifier base; |
| 2983 | if (!_guess_expression_type(completion_context, subscript->base, base)) { |
| 2984 | break; |
| 2985 | } |
| 2986 | |
| 2987 | _find_identifiers_in_base(base, false, options, 0); |
| 2988 | } break; |
| 2989 | case GDScriptParser::COMPLETION_TYPE_ATTRIBUTE: { |
| 2990 | if (!completion_context.current_class) { |
| 2991 | break; |
| 2992 | } |
| 2993 | const GDScriptParser::TypeNode *type = static_cast<const GDScriptParser::TypeNode *>(completion_context.node); |
| 2994 | bool found = true; |
| 2995 | GDScriptCompletionIdentifier base; |
| 2996 | base.type.kind = GDScriptParser::DataType::CLASS; |
| 2997 | base.type.type_source = GDScriptParser::DataType::INFERRED; |
| 2998 | base.type.is_constant = true; |
| 2999 | base.type.class_type = completion_context.current_class; |
| 3000 | base.value = completion_context.base; |
| 3001 | |
| 3002 | for (int i = 0; i < completion_context.current_argument; i++) { |
| 3003 | GDScriptCompletionIdentifier ci; |
| 3004 | if (!_guess_identifier_type_from_base(completion_context, base, type->type_chain[i]->name, ci)) { |
| 3005 | found = false; |
| 3006 | break; |
| 3007 | } |
| 3008 | base = ci; |
| 3009 | } |
| 3010 | |
| 3011 | // TODO: Improve this to only list types. |
| 3012 | if (found) { |
| 3013 | _find_identifiers_in_base(base, false, options, 0); |
| 3014 | } |
| 3015 | r_forced = true; |
| 3016 | } break; |
| 3017 | case GDScriptParser::COMPLETION_RESOURCE_PATH: { |
| 3018 | if (EDITOR_GET("text_editor/completion/complete_file_paths" )) { |
| 3019 | _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), options); |
| 3020 | r_forced = true; |
| 3021 | } |
| 3022 | } break; |
| 3023 | case GDScriptParser::COMPLETION_CALL_ARGUMENTS: { |
| 3024 | if (!completion_context.node) { |
| 3025 | break; |
| 3026 | } |
| 3027 | _find_call_arguments(completion_context, completion_context.node, completion_context.current_argument, options, r_forced, r_call_hint); |
| 3028 | } break; |
| 3029 | case GDScriptParser::COMPLETION_OVERRIDE_METHOD: { |
| 3030 | GDScriptParser::DataType native_type = completion_context.current_class->base_type; |
| 3031 | while (native_type.is_set() && native_type.kind != GDScriptParser::DataType::NATIVE) { |
| 3032 | switch (native_type.kind) { |
| 3033 | case GDScriptParser::DataType::CLASS: { |
| 3034 | native_type = native_type.class_type->base_type; |
| 3035 | } break; |
| 3036 | default: { |
| 3037 | native_type.kind = GDScriptParser::DataType::UNRESOLVED; |
| 3038 | } break; |
| 3039 | } |
| 3040 | } |
| 3041 | |
| 3042 | if (!native_type.is_set()) { |
| 3043 | break; |
| 3044 | } |
| 3045 | |
| 3046 | StringName class_name = native_type.native_type; |
| 3047 | if (!ClassDB::class_exists(class_name)) { |
| 3048 | break; |
| 3049 | } |
| 3050 | |
| 3051 | bool use_type_hint = EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints" ).operator bool(); |
| 3052 | |
| 3053 | List<MethodInfo> virtual_methods; |
| 3054 | ClassDB::get_virtual_methods(class_name, &virtual_methods); |
| 3055 | |
| 3056 | { |
| 3057 | // Not truly a virtual method, but can also be "overridden". |
| 3058 | MethodInfo static_init("_static_init" ); |
| 3059 | static_init.return_val.type = Variant::NIL; |
| 3060 | static_init.flags |= METHOD_FLAG_STATIC | METHOD_FLAG_VIRTUAL; |
| 3061 | virtual_methods.push_back(static_init); |
| 3062 | } |
| 3063 | |
| 3064 | for (const MethodInfo &mi : virtual_methods) { |
| 3065 | String method_hint = mi.name; |
| 3066 | if (method_hint.contains(":" )) { |
| 3067 | method_hint = method_hint.get_slice(":" , 0); |
| 3068 | } |
| 3069 | method_hint += "(" ; |
| 3070 | |
| 3071 | if (mi.arguments.size()) { |
| 3072 | for (int i = 0; i < mi.arguments.size(); i++) { |
| 3073 | if (i > 0) { |
| 3074 | method_hint += ", " ; |
| 3075 | } |
| 3076 | String arg = mi.arguments[i].name; |
| 3077 | if (arg.contains(":" )) { |
| 3078 | arg = arg.substr(0, arg.find(":" )); |
| 3079 | } |
| 3080 | method_hint += arg; |
| 3081 | if (use_type_hint) { |
| 3082 | method_hint += ": " + _get_visual_datatype(mi.arguments[i], true, class_name); |
| 3083 | } |
| 3084 | } |
| 3085 | } |
| 3086 | method_hint += ")" ; |
| 3087 | if (use_type_hint) { |
| 3088 | method_hint += " -> " + _get_visual_datatype(mi.return_val, false, class_name); |
| 3089 | } |
| 3090 | method_hint += ":" ; |
| 3091 | |
| 3092 | ScriptLanguage::CodeCompletionOption option(method_hint, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); |
| 3093 | options.insert(option.display, option); |
| 3094 | } |
| 3095 | } break; |
| 3096 | case GDScriptParser::COMPLETION_GET_NODE: { |
| 3097 | // Handles the `$Node/Path` or `$"Some NodePath"` syntax specifically. |
| 3098 | if (p_owner) { |
| 3099 | List<String> opts; |
| 3100 | p_owner->get_argument_options("get_node" , 0, &opts); |
| 3101 | |
| 3102 | for (const String &E : opts) { |
| 3103 | r_forced = true; |
| 3104 | String opt = E.strip_edges(); |
| 3105 | if (opt.is_quoted()) { |
| 3106 | // Remove quotes so that we can handle user preferred quote style, |
| 3107 | // or handle NodePaths which are valid identifiers and don't need quotes. |
| 3108 | opt = opt.unquote(); |
| 3109 | } |
| 3110 | // The path needs quotes if it's not a valid identifier (with an exception |
| 3111 | // for "/" as path separator, which also doesn't require quotes). |
| 3112 | if (!opt.replace("/" , "_" ).is_valid_identifier()) { |
| 3113 | // Ignore quote_style and just use double quotes for paths with apostrophes. |
| 3114 | // Double quotes don't need to be checked because they're not valid in node and property names. |
| 3115 | opt = opt.quote(opt.contains("'" ) ? "\"" : quote_style); // Handle user preference. |
| 3116 | } |
| 3117 | ScriptLanguage::CodeCompletionOption option(opt, ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH); |
| 3118 | options.insert(option.display, option); |
| 3119 | } |
| 3120 | |
| 3121 | // Get autoloads. |
| 3122 | for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) { |
| 3123 | String path = "/root/" + E.key; |
| 3124 | ScriptLanguage::CodeCompletionOption option(path.quote(quote_style), ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH); |
| 3125 | options.insert(option.display, option); |
| 3126 | } |
| 3127 | } |
| 3128 | } break; |
| 3129 | case GDScriptParser::COMPLETION_SUPER_METHOD: { |
| 3130 | if (!completion_context.current_class) { |
| 3131 | break; |
| 3132 | } |
| 3133 | _find_identifiers_in_class(completion_context.current_class, true, false, true, options, 0); |
| 3134 | } break; |
| 3135 | } |
| 3136 | |
| 3137 | for (const KeyValue<String, ScriptLanguage::CodeCompletionOption> &E : options) { |
| 3138 | r_options->push_back(E.value); |
| 3139 | } |
| 3140 | |
| 3141 | return OK; |
| 3142 | } |
| 3143 | |
| 3144 | #else |
| 3145 | |
| 3146 | 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) { |
| 3147 | return OK; |
| 3148 | } |
| 3149 | |
| 3150 | #endif |
| 3151 | |
| 3152 | //////// END COMPLETION ////////// |
| 3153 | |
| 3154 | String GDScriptLanguage::_get_indentation() const { |
| 3155 | #ifdef TOOLS_ENABLED |
| 3156 | if (Engine::get_singleton()->is_editor_hint()) { |
| 3157 | bool use_space_indentation = EDITOR_GET("text_editor/behavior/indent/type" ); |
| 3158 | |
| 3159 | if (use_space_indentation) { |
| 3160 | int indent_size = EDITOR_GET("text_editor/behavior/indent/size" ); |
| 3161 | return String(" " ).repeat(indent_size); |
| 3162 | } |
| 3163 | } |
| 3164 | #endif |
| 3165 | return "\t" ; |
| 3166 | } |
| 3167 | |
| 3168 | void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_to_line) const { |
| 3169 | String indent = _get_indentation(); |
| 3170 | |
| 3171 | Vector<String> lines = p_code.split("\n" ); |
| 3172 | List<int> indent_stack; |
| 3173 | |
| 3174 | for (int i = 0; i < lines.size(); i++) { |
| 3175 | String l = lines[i]; |
| 3176 | int tc = 0; |
| 3177 | for (int j = 0; j < l.length(); j++) { |
| 3178 | if (l[j] == ' ' || l[j] == '\t') { |
| 3179 | tc++; |
| 3180 | } else { |
| 3181 | break; |
| 3182 | } |
| 3183 | } |
| 3184 | |
| 3185 | String st = l.substr(tc, l.length()).strip_edges(); |
| 3186 | if (st.is_empty() || st.begins_with("#" )) { |
| 3187 | continue; //ignore! |
| 3188 | } |
| 3189 | |
| 3190 | int ilevel = 0; |
| 3191 | if (indent_stack.size()) { |
| 3192 | ilevel = indent_stack.back()->get(); |
| 3193 | } |
| 3194 | |
| 3195 | if (tc > ilevel) { |
| 3196 | indent_stack.push_back(tc); |
| 3197 | } else if (tc < ilevel) { |
| 3198 | while (indent_stack.size() && indent_stack.back()->get() > tc) { |
| 3199 | indent_stack.pop_back(); |
| 3200 | } |
| 3201 | |
| 3202 | if (indent_stack.size() && indent_stack.back()->get() != tc) { |
| 3203 | indent_stack.push_back(tc); // this is not right but gets the job done |
| 3204 | } |
| 3205 | } |
| 3206 | |
| 3207 | if (i >= p_from_line) { |
| 3208 | l = indent.repeat(indent_stack.size()) + st; |
| 3209 | } else if (i > p_to_line) { |
| 3210 | break; |
| 3211 | } |
| 3212 | |
| 3213 | lines.write[i] = l; |
| 3214 | } |
| 3215 | |
| 3216 | p_code = "" ; |
| 3217 | for (int i = 0; i < lines.size(); i++) { |
| 3218 | if (i > 0) { |
| 3219 | p_code += "\n" ; |
| 3220 | } |
| 3221 | p_code += lines[i]; |
| 3222 | } |
| 3223 | } |
| 3224 | |
| 3225 | #ifdef TOOLS_ENABLED |
| 3226 | |
| 3227 | static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, const String &p_symbol, bool p_is_function, GDScriptLanguage::LookupResult &r_result) { |
| 3228 | GDScriptParser::DataType base_type = p_base; |
| 3229 | |
| 3230 | while (base_type.is_set()) { |
| 3231 | switch (base_type.kind) { |
| 3232 | case GDScriptParser::DataType::CLASS: { |
| 3233 | if (base_type.class_type) { |
| 3234 | if (base_type.class_type->has_member(p_symbol)) { |
| 3235 | r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION; |
| 3236 | r_result.location = base_type.class_type->get_member(p_symbol).get_line(); |
| 3237 | r_result.class_path = base_type.script_path; |
| 3238 | Error err = OK; |
| 3239 | r_result.script = GDScriptCache::get_shallow_script(r_result.class_path, err); |
| 3240 | return err; |
| 3241 | } |
| 3242 | base_type = base_type.class_type->base_type; |
| 3243 | } |
| 3244 | } break; |
| 3245 | case GDScriptParser::DataType::SCRIPT: { |
| 3246 | Ref<Script> scr = base_type.script_type; |
| 3247 | if (scr.is_valid()) { |
| 3248 | int line = scr->get_member_line(p_symbol); |
| 3249 | if (line >= 0) { |
| 3250 | r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION; |
| 3251 | r_result.location = line; |
| 3252 | r_result.script = scr; |
| 3253 | return OK; |
| 3254 | } |
| 3255 | Ref<Script> base_script = scr->get_base_script(); |
| 3256 | if (base_script.is_valid()) { |
| 3257 | base_type.script_type = base_script; |
| 3258 | } else { |
| 3259 | base_type.kind = GDScriptParser::DataType::NATIVE; |
| 3260 | base_type.native_type = scr->get_instance_base_type(); |
| 3261 | } |
| 3262 | } else { |
| 3263 | base_type.kind = GDScriptParser::DataType::UNRESOLVED; |
| 3264 | } |
| 3265 | } break; |
| 3266 | case GDScriptParser::DataType::NATIVE: { |
| 3267 | StringName class_name = base_type.native_type; |
| 3268 | if (!ClassDB::class_exists(class_name)) { |
| 3269 | base_type.kind = GDScriptParser::DataType::UNRESOLVED; |
| 3270 | break; |
| 3271 | } |
| 3272 | |
| 3273 | if (ClassDB::has_method(class_name, p_symbol, true)) { |
| 3274 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; |
| 3275 | r_result.class_name = base_type.native_type; |
| 3276 | r_result.class_member = p_symbol; |
| 3277 | return OK; |
| 3278 | } |
| 3279 | |
| 3280 | List<MethodInfo> virtual_methods; |
| 3281 | ClassDB::get_virtual_methods(class_name, &virtual_methods, true); |
| 3282 | for (const MethodInfo &E : virtual_methods) { |
| 3283 | if (E.name == p_symbol) { |
| 3284 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; |
| 3285 | r_result.class_name = base_type.native_type; |
| 3286 | r_result.class_member = p_symbol; |
| 3287 | return OK; |
| 3288 | } |
| 3289 | } |
| 3290 | |
| 3291 | if (ClassDB::has_signal(class_name, p_symbol, true)) { |
| 3292 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_SIGNAL; |
| 3293 | r_result.class_name = base_type.native_type; |
| 3294 | r_result.class_member = p_symbol; |
| 3295 | return OK; |
| 3296 | } |
| 3297 | |
| 3298 | StringName enum_name = ClassDB::get_integer_constant_enum(class_name, p_symbol, true); |
| 3299 | if (enum_name != StringName()) { |
| 3300 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM; |
| 3301 | r_result.class_name = base_type.native_type; |
| 3302 | r_result.class_member = enum_name; |
| 3303 | return OK; |
| 3304 | } |
| 3305 | |
| 3306 | List<String> constants; |
| 3307 | ClassDB::get_integer_constant_list(class_name, &constants, true); |
| 3308 | for (const String &E : constants) { |
| 3309 | if (E == p_symbol) { |
| 3310 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; |
| 3311 | r_result.class_name = base_type.native_type; |
| 3312 | r_result.class_member = p_symbol; |
| 3313 | return OK; |
| 3314 | } |
| 3315 | } |
| 3316 | |
| 3317 | if (ClassDB::has_property(class_name, p_symbol, true)) { |
| 3318 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY; |
| 3319 | r_result.class_name = base_type.native_type; |
| 3320 | r_result.class_member = p_symbol; |
| 3321 | return OK; |
| 3322 | } |
| 3323 | |
| 3324 | StringName parent = ClassDB::get_parent_class(class_name); |
| 3325 | if (parent != StringName()) { |
| 3326 | base_type.native_type = parent; |
| 3327 | } else { |
| 3328 | base_type.kind = GDScriptParser::DataType::UNRESOLVED; |
| 3329 | } |
| 3330 | } break; |
| 3331 | case GDScriptParser::DataType::BUILTIN: { |
| 3332 | base_type.kind = GDScriptParser::DataType::UNRESOLVED; |
| 3333 | |
| 3334 | if (Variant::has_constant(base_type.builtin_type, p_symbol)) { |
| 3335 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; |
| 3336 | r_result.class_name = Variant::get_type_name(base_type.builtin_type); |
| 3337 | r_result.class_member = p_symbol; |
| 3338 | return OK; |
| 3339 | } |
| 3340 | |
| 3341 | Variant v; |
| 3342 | Ref<RefCounted> v_ref; |
| 3343 | if (base_type.builtin_type == Variant::OBJECT) { |
| 3344 | v_ref.instantiate(); |
| 3345 | v = v_ref; |
| 3346 | } else { |
| 3347 | Callable::CallError err; |
| 3348 | Variant::construct(base_type.builtin_type, v, nullptr, 0, err); |
| 3349 | if (err.error != Callable::CallError::CALL_OK) { |
| 3350 | break; |
| 3351 | } |
| 3352 | } |
| 3353 | |
| 3354 | if (v.has_method(p_symbol)) { |
| 3355 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; |
| 3356 | r_result.class_name = Variant::get_type_name(base_type.builtin_type); |
| 3357 | r_result.class_member = p_symbol; |
| 3358 | return OK; |
| 3359 | } |
| 3360 | |
| 3361 | bool valid = false; |
| 3362 | v.get(p_symbol, &valid); |
| 3363 | if (valid) { |
| 3364 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY; |
| 3365 | r_result.class_name = Variant::get_type_name(base_type.builtin_type); |
| 3366 | r_result.class_member = p_symbol; |
| 3367 | return OK; |
| 3368 | } |
| 3369 | } break; |
| 3370 | default: { |
| 3371 | base_type.kind = GDScriptParser::DataType::UNRESOLVED; |
| 3372 | } break; |
| 3373 | } |
| 3374 | } |
| 3375 | |
| 3376 | return ERR_CANT_RESOLVE; |
| 3377 | } |
| 3378 | |
| 3379 | ::Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) { |
| 3380 | // Before parsing, try the usual stuff. |
| 3381 | if (ClassDB::class_exists(p_symbol)) { |
| 3382 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS; |
| 3383 | r_result.class_name = p_symbol; |
| 3384 | return OK; |
| 3385 | } |
| 3386 | |
| 3387 | for (int i = 0; i < Variant::VARIANT_MAX; i++) { |
| 3388 | Variant::Type t = Variant::Type(i); |
| 3389 | if (Variant::get_type_name(t) == p_symbol) { |
| 3390 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS; |
| 3391 | r_result.class_name = Variant::get_type_name(t); |
| 3392 | return OK; |
| 3393 | } |
| 3394 | } |
| 3395 | |
| 3396 | if ("PI" == p_symbol || "TAU" == p_symbol || "INF" == p_symbol || "NAN" == p_symbol) { |
| 3397 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; |
| 3398 | r_result.class_name = "@GDScript" ; |
| 3399 | r_result.class_member = p_symbol; |
| 3400 | return OK; |
| 3401 | } |
| 3402 | |
| 3403 | GDScriptParser parser; |
| 3404 | parser.parse(p_code, p_path, true); |
| 3405 | |
| 3406 | GDScriptParser::CompletionContext context = parser.get_completion_context(); |
| 3407 | context.base = p_owner; |
| 3408 | |
| 3409 | // Allows class functions with the names like built-ins to be handled properly. |
| 3410 | if (context.type != GDScriptParser::COMPLETION_ATTRIBUTE) { |
| 3411 | // Need special checks for assert and preload as they are technically |
| 3412 | // keywords, so are not registered in GDScriptUtilityFunctions. |
| 3413 | if (GDScriptUtilityFunctions::function_exists(p_symbol) || "assert" == p_symbol || "preload" == p_symbol) { |
| 3414 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; |
| 3415 | r_result.class_name = "@GDScript" ; |
| 3416 | r_result.class_member = p_symbol; |
| 3417 | return OK; |
| 3418 | } |
| 3419 | } |
| 3420 | |
| 3421 | GDScriptAnalyzer analyzer(&parser); |
| 3422 | analyzer.analyze(); |
| 3423 | |
| 3424 | if (context.current_class && context.current_class->extends.size() > 0) { |
| 3425 | bool success = false; |
| 3426 | ClassDB::get_integer_constant(context.current_class->extends[0]->name, p_symbol, &success); |
| 3427 | if (success) { |
| 3428 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; |
| 3429 | r_result.class_name = context.current_class->extends[0]->name; |
| 3430 | r_result.class_member = p_symbol; |
| 3431 | return OK; |
| 3432 | } |
| 3433 | } |
| 3434 | |
| 3435 | bool is_function = false; |
| 3436 | |
| 3437 | switch (context.type) { |
| 3438 | case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD: { |
| 3439 | if (!Variant::has_builtin_method(context.builtin_type, StringName(p_symbol))) { |
| 3440 | // A constant. |
| 3441 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; |
| 3442 | r_result.class_name = Variant::get_type_name(context.builtin_type); |
| 3443 | r_result.class_member = p_symbol; |
| 3444 | return OK; |
| 3445 | } |
| 3446 | // A method. |
| 3447 | GDScriptParser::DataType base_type; |
| 3448 | base_type.kind = GDScriptParser::DataType::BUILTIN; |
| 3449 | base_type.builtin_type = context.builtin_type; |
| 3450 | if (_lookup_symbol_from_base(base_type, p_symbol, true, r_result) == OK) { |
| 3451 | return OK; |
| 3452 | } |
| 3453 | } break; |
| 3454 | case GDScriptParser::COMPLETION_SUPER_METHOD: |
| 3455 | case GDScriptParser::COMPLETION_METHOD: { |
| 3456 | is_function = true; |
| 3457 | [[fallthrough]]; |
| 3458 | } |
| 3459 | case GDScriptParser::COMPLETION_ASSIGN: |
| 3460 | case GDScriptParser::COMPLETION_CALL_ARGUMENTS: |
| 3461 | case GDScriptParser::COMPLETION_IDENTIFIER: |
| 3462 | case GDScriptParser::COMPLETION_PROPERTY_METHOD: { |
| 3463 | GDScriptParser::DataType base_type; |
| 3464 | if (context.current_class) { |
| 3465 | if (context.type != GDScriptParser::COMPLETION_SUPER_METHOD) { |
| 3466 | base_type = context.current_class->get_datatype(); |
| 3467 | } else { |
| 3468 | base_type = context.current_class->base_type; |
| 3469 | } |
| 3470 | } else { |
| 3471 | break; |
| 3472 | } |
| 3473 | |
| 3474 | if (!is_function && context.current_suite) { |
| 3475 | // Lookup local variables. |
| 3476 | const GDScriptParser::SuiteNode *suite = context.current_suite; |
| 3477 | while (suite) { |
| 3478 | if (suite->has_local(p_symbol)) { |
| 3479 | r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION; |
| 3480 | r_result.location = suite->get_local(p_symbol).start_line; |
| 3481 | return OK; |
| 3482 | } |
| 3483 | suite = suite->parent_block; |
| 3484 | } |
| 3485 | } |
| 3486 | |
| 3487 | if (_lookup_symbol_from_base(base_type, p_symbol, is_function, r_result) == OK) { |
| 3488 | return OK; |
| 3489 | } |
| 3490 | |
| 3491 | if (!is_function) { |
| 3492 | // Guess in autoloads as singletons. |
| 3493 | if (ProjectSettings::get_singleton()->has_autoload(p_symbol)) { |
| 3494 | const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(p_symbol); |
| 3495 | if (autoload.is_singleton) { |
| 3496 | String scr_path = autoload.path; |
| 3497 | if (!scr_path.ends_with(".gd" )) { |
| 3498 | // Not a script, try find the script anyway, |
| 3499 | // may have some success. |
| 3500 | scr_path = scr_path.get_basename() + ".gd" ; |
| 3501 | } |
| 3502 | |
| 3503 | if (FileAccess::exists(scr_path)) { |
| 3504 | r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION; |
| 3505 | r_result.location = 0; |
| 3506 | r_result.script = ResourceLoader::load(scr_path); |
| 3507 | return OK; |
| 3508 | } |
| 3509 | } |
| 3510 | } |
| 3511 | |
| 3512 | // Global. |
| 3513 | HashMap<StringName, int> classes = GDScriptLanguage::get_singleton()->get_global_map(); |
| 3514 | if (classes.has(p_symbol)) { |
| 3515 | Variant value = GDScriptLanguage::get_singleton()->get_global_array()[classes[p_symbol]]; |
| 3516 | if (value.get_type() == Variant::OBJECT) { |
| 3517 | Object *obj = value; |
| 3518 | if (obj) { |
| 3519 | if (Object::cast_to<GDScriptNativeClass>(obj)) { |
| 3520 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS; |
| 3521 | r_result.class_name = Object::cast_to<GDScriptNativeClass>(obj)->get_name(); |
| 3522 | } else { |
| 3523 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS; |
| 3524 | r_result.class_name = obj->get_class(); |
| 3525 | } |
| 3526 | |
| 3527 | // proxy class remove the underscore. |
| 3528 | if (r_result.class_name.begins_with("_" )) { |
| 3529 | r_result.class_name = r_result.class_name.substr(1); |
| 3530 | } |
| 3531 | return OK; |
| 3532 | } |
| 3533 | } else { |
| 3534 | /* |
| 3535 | // Because get_integer_constant_enum and get_integer_constant don't work on @GlobalScope |
| 3536 | // We cannot determine the exact nature of the identifier here |
| 3537 | // Otherwise these codes would work |
| 3538 | StringName enumName = ClassDB::get_integer_constant_enum("@GlobalScope", p_symbol, true); |
| 3539 | if (enumName != nullptr) { |
| 3540 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM; |
| 3541 | r_result.class_name = "@GlobalScope"; |
| 3542 | r_result.class_member = enumName; |
| 3543 | return OK; |
| 3544 | } |
| 3545 | else { |
| 3546 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; |
| 3547 | r_result.class_name = "@GlobalScope"; |
| 3548 | r_result.class_member = p_symbol; |
| 3549 | return OK; |
| 3550 | }*/ |
| 3551 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE; |
| 3552 | r_result.class_name = "@GlobalScope" ; |
| 3553 | r_result.class_member = p_symbol; |
| 3554 | return OK; |
| 3555 | } |
| 3556 | } else { |
| 3557 | List<StringName> utility_functions; |
| 3558 | Variant::get_utility_function_list(&utility_functions); |
| 3559 | if (utility_functions.find(p_symbol) != nullptr) { |
| 3560 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE; |
| 3561 | r_result.class_name = "@GlobalScope" ; |
| 3562 | r_result.class_member = p_symbol; |
| 3563 | return OK; |
| 3564 | } |
| 3565 | } |
| 3566 | } |
| 3567 | } break; |
| 3568 | case GDScriptParser::COMPLETION_ATTRIBUTE_METHOD: { |
| 3569 | is_function = true; |
| 3570 | [[fallthrough]]; |
| 3571 | } |
| 3572 | case GDScriptParser::COMPLETION_ATTRIBUTE: { |
| 3573 | if (context.node->type != GDScriptParser::Node::SUBSCRIPT) { |
| 3574 | break; |
| 3575 | } |
| 3576 | const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(context.node); |
| 3577 | if (!subscript->is_attribute) { |
| 3578 | break; |
| 3579 | } |
| 3580 | GDScriptCompletionIdentifier base; |
| 3581 | |
| 3582 | bool found_type = _get_subscript_type(context, subscript, base.type); |
| 3583 | if (!found_type && !_guess_expression_type(context, subscript->base, base)) { |
| 3584 | break; |
| 3585 | } |
| 3586 | |
| 3587 | if (_lookup_symbol_from_base(base.type, p_symbol, is_function, r_result) == OK) { |
| 3588 | return OK; |
| 3589 | } |
| 3590 | } break; |
| 3591 | case GDScriptParser::COMPLETION_TYPE_ATTRIBUTE: { |
| 3592 | if (context.node == nullptr || context.node->type != GDScriptParser::Node::TYPE) { |
| 3593 | break; |
| 3594 | } |
| 3595 | const GDScriptParser::TypeNode *type = static_cast<const GDScriptParser::TypeNode *>(context.node); |
| 3596 | |
| 3597 | GDScriptParser::DataType base_type; |
| 3598 | const GDScriptParser::IdentifierNode *prev = nullptr; |
| 3599 | for (const GDScriptParser::IdentifierNode *E : type->type_chain) { |
| 3600 | if (E->name == p_symbol && prev != nullptr) { |
| 3601 | base_type = prev->get_datatype(); |
| 3602 | break; |
| 3603 | } |
| 3604 | prev = E; |
| 3605 | } |
| 3606 | if (base_type.kind != GDScriptParser::DataType::CLASS) { |
| 3607 | GDScriptCompletionIdentifier base; |
| 3608 | if (!_guess_expression_type(context, prev, base)) { |
| 3609 | break; |
| 3610 | } |
| 3611 | base_type = base.type; |
| 3612 | } |
| 3613 | |
| 3614 | if (_lookup_symbol_from_base(base_type, p_symbol, is_function, r_result) == OK) { |
| 3615 | return OK; |
| 3616 | } |
| 3617 | } break; |
| 3618 | case GDScriptParser::COMPLETION_OVERRIDE_METHOD: { |
| 3619 | GDScriptParser::DataType base_type = context.current_class->base_type; |
| 3620 | |
| 3621 | if (_lookup_symbol_from_base(base_type, p_symbol, true, r_result) == OK) { |
| 3622 | return OK; |
| 3623 | } |
| 3624 | } break; |
| 3625 | case GDScriptParser::COMPLETION_PROPERTY_DECLARATION_OR_TYPE: |
| 3626 | case GDScriptParser::COMPLETION_TYPE_NAME_OR_VOID: |
| 3627 | case GDScriptParser::COMPLETION_TYPE_NAME: { |
| 3628 | GDScriptParser::DataType base_type = context.current_class->get_datatype(); |
| 3629 | |
| 3630 | if (_lookup_symbol_from_base(base_type, p_symbol, false, r_result) == OK) { |
| 3631 | return OK; |
| 3632 | } |
| 3633 | } break; |
| 3634 | case GDScriptParser::COMPLETION_ANNOTATION: { |
| 3635 | const String annotation_symbol = "@" + p_symbol; |
| 3636 | if (parser.annotation_exists(annotation_symbol)) { |
| 3637 | r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ANNOTATION; |
| 3638 | r_result.class_name = "@GDScript" ; |
| 3639 | r_result.class_member = annotation_symbol; |
| 3640 | return OK; |
| 3641 | } |
| 3642 | } break; |
| 3643 | default: { |
| 3644 | } |
| 3645 | } |
| 3646 | |
| 3647 | return ERR_CANT_RESOLVE; |
| 3648 | } |
| 3649 | |
| 3650 | #endif |
| 3651 | |