/*************************************************************************/ /* gdscript_analyzer.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #include "gdscript_analyzer.h" #include "core/config/engine.h" #include "core/config/project_settings.h" #include "core/io/file_access.h" #include "core/io/resource_loader.h" #include "core/object/class_db.h" #include "core/object/script_language.h" #include "core/templates/hash_map.h" #include "gdscript.h" #include "gdscript_utility_functions.h" static MethodInfo info_from_utility_func(const StringName &p_function) { ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo()); MethodInfo info(p_function); if (Variant::has_utility_function_return_value(p_function)) { info.return_val.type = Variant::get_utility_function_return_type(p_function); if (info.return_val.type == Variant::NIL) { info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; } } if (Variant::is_utility_function_vararg(p_function)) { info.flags |= METHOD_FLAG_VARARG; } else { for (int i = 0; i < Variant::get_utility_function_argument_count(p_function); i++) { PropertyInfo pi; #ifdef DEBUG_METHODS_ENABLED pi.name = Variant::get_utility_function_argument_name(p_function, i); #else pi.name = "arg" + itos(i + 1); #endif pi.type = Variant::get_utility_function_argument_type(p_function, i); if (pi.type == Variant::NIL) { pi.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; } info.arguments.push_back(pi); } } return info; } static GDScriptParser::DataType make_callable_type(const MethodInfo &p_info) { GDScriptParser::DataType type; type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; type.kind = GDScriptParser::DataType::BUILTIN; type.builtin_type = Variant::CALLABLE; type.is_constant = true; type.method_info = p_info; return type; } static GDScriptParser::DataType make_signal_type(const MethodInfo &p_info) { GDScriptParser::DataType type; type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; type.kind = GDScriptParser::DataType::BUILTIN; type.builtin_type = Variant::SIGNAL; type.is_constant = true; type.method_info = p_info; return type; } static GDScriptParser::DataType make_native_meta_type(const StringName &p_class_name) { GDScriptParser::DataType type; type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; type.kind = GDScriptParser::DataType::NATIVE; type.builtin_type = Variant::OBJECT; type.is_constant = true; type.native_type = p_class_name; type.is_meta_type = true; return type; } static GDScriptParser::DataType make_native_enum_type(const StringName &p_native_class, const StringName &p_enum_name) { GDScriptParser::DataType type; type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; type.kind = GDScriptParser::DataType::ENUM; type.builtin_type = Variant::OBJECT; type.is_constant = true; type.is_meta_type = true; List enum_values; ClassDB::get_enum_constants(p_native_class, p_enum_name, &enum_values); for (const StringName &E : enum_values) { type.enum_values[E] = ClassDB::get_integer_constant(p_native_class, E); } return type; } static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) { GDScriptParser::DataType type; type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; type.kind = GDScriptParser::DataType::BUILTIN; type.builtin_type = p_type; type.is_constant = true; type.is_meta_type = true; return type; } bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class) { if (p_class->members_indices.has(p_member_name)) { int index = p_class->members_indices[p_member_name]; const GDScriptParser::ClassNode::Member *member = &p_class->members[index]; if (member->type == GDScriptParser::ClassNode::Member::VARIABLE || member->type == GDScriptParser::ClassNode::Member::CONSTANT || member->type == GDScriptParser::ClassNode::Member::ENUM || member->type == GDScriptParser::ClassNode::Member::ENUM_VALUE || member->type == GDScriptParser::ClassNode::Member::CLASS || member->type == GDScriptParser::ClassNode::Member::SIGNAL) { return true; } } return false; } bool GDScriptAnalyzer::has_member_name_conflict_in_native_type(const StringName &p_member_name, const StringName &p_native_type_string) { if (ClassDB::has_signal(p_native_type_string, p_member_name)) { return true; } if (ClassDB::has_property(p_native_type_string, p_member_name)) { return true; } if (ClassDB::has_integer_constant(p_native_type_string, p_member_name)) { return true; } return false; } Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string) { if (has_member_name_conflict_in_native_type(p_member_name, p_native_type_string)) { push_error(vformat(R"(Member "%s" redefined (original in native class '%s'))", p_member_name, p_native_type_string), p_member_node); return ERR_PARSE_ERROR; } if (class_exists(p_member_name)) { push_error(vformat(R"(The member "%s" shadows a native class.)", p_member_name), p_member_node); return ERR_PARSE_ERROR; } if (GDScriptParser::get_builtin_type(p_member_name) != Variant::VARIANT_MAX) { push_error(vformat(R"(The member "%s" cannot have the same name as a builtin type.)", p_member_name), p_member_node); return ERR_PARSE_ERROR; } return OK; } Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node) { const GDScriptParser::DataType *current_data_type = &p_class_node->base_type; while (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::CLASS) { GDScriptParser::ClassNode *current_class_node = current_data_type->class_type; if (has_member_name_conflict_in_script_class(p_member_name, current_class_node)) { push_error(vformat(R"(The member "%s" already exists in a parent class.)", p_member_name), p_member_node); return ERR_PARSE_ERROR; } current_data_type = ¤t_class_node->base_type; } if (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::NATIVE) { if (current_data_type->native_type != StringName("")) { return check_native_member_name_conflict( p_member_name, p_member_node, current_data_type->native_type); } } return OK; } Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) { if (p_class->base_type.is_set()) { // Already resolved return OK; } if (p_class == parser->head) { if (p_class->identifier) { p_class->fqcn = p_class->identifier->name; } else { p_class->fqcn = parser->script_path; } } else { p_class->fqcn = p_class->outer->fqcn + "::" + String(p_class->identifier->name); } if (p_class->identifier) { StringName class_name = p_class->identifier->name; if (class_exists(class_name)) { push_error(vformat(R"(Class "%s" hides a native class.)", class_name), p_class->identifier); } else if (ScriptServer::is_global_class(class_name) && (ScriptServer::get_global_class_path(class_name) != parser->script_path || p_class != parser->head)) { push_error(vformat(R"(Class "%s" hides a global script class.)", class_name), p_class->identifier); } else if (ProjectSettings::get_singleton()->has_autoload(class_name) && ProjectSettings::get_singleton()->get_autoload(class_name).is_singleton) { push_error(vformat(R"(Class "%s" hides an autoload singleton.)", class_name), p_class->identifier); } } GDScriptParser::DataType result; // Set datatype for class. GDScriptParser::DataType class_type; class_type.is_constant = true; class_type.is_meta_type = true; class_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; class_type.kind = GDScriptParser::DataType::CLASS; class_type.class_type = p_class; class_type.script_path = parser->script_path; class_type.builtin_type = Variant::OBJECT; p_class->set_datatype(class_type); if (!p_class->extends_used) { result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; result.kind = GDScriptParser::DataType::NATIVE; result.native_type = "RefCounted"; } else { result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; GDScriptParser::DataType base; int extends_index = 0; if (!p_class->extends_path.is_empty()) { if (p_class->extends_path.is_relative_path()) { p_class->extends_path = class_type.script_path.get_base_dir().plus_file(p_class->extends_path).simplify_path(); } Ref parser = get_parser_for(p_class->extends_path); if (parser.is_null()) { push_error(vformat(R"(Could not resolve super class path "%s".)", p_class->extends_path), p_class); return ERR_PARSE_ERROR; } Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", p_class->extends_path), p_class); return err; } base = parser->get_parser()->head->get_datatype(); } else { if (p_class->extends.is_empty()) { return ERR_PARSE_ERROR; } const StringName &name = p_class->extends[extends_index++]; base.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; if (ScriptServer::is_global_class(name)) { String base_path = ScriptServer::get_global_class_path(name); if (base_path == parser->script_path) { base = parser->head->get_datatype(); } else { Ref parser = get_parser_for(base_path); if (parser.is_null()) { push_error(vformat(R"(Could not resolve super class "%s".)", name), p_class); return ERR_PARSE_ERROR; } Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); return err; } base = parser->get_parser()->head->get_datatype(); } } else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) { const ProjectSettings::AutoloadInfo &info = ProjectSettings::get_singleton()->get_autoload(name); if (info.path.get_extension().to_lower() != ".gd") { push_error(vformat(R"(Singleton %s is not a GDScript.)", info.name), p_class); return ERR_PARSE_ERROR; } Ref parser = get_parser_for(info.path); if (parser.is_null()) { push_error(vformat(R"(Could not parse singleton from "%s".)", info.path), p_class); return ERR_PARSE_ERROR; } Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); return err; } } else if (class_exists(name) && ClassDB::can_instantiate(name)) { base.kind = GDScriptParser::DataType::NATIVE; base.native_type = name; } else { // Look for other classes in script. GDScriptParser::ClassNode *look_class = p_class; bool found = false; while (look_class != nullptr) { if (look_class->identifier && look_class->identifier->name == name) { if (!look_class->get_datatype().is_set()) { Error err = resolve_inheritance(look_class, false); if (err) { return err; } } base = look_class->get_datatype(); found = true; break; } if (look_class->members_indices.has(name) && look_class->get_member(name).type == GDScriptParser::ClassNode::Member::CLASS) { GDScriptParser::ClassNode::Member member = look_class->get_member(name); if (!member.m_class->get_datatype().is_set()) { Error err = resolve_inheritance(member.m_class, false); if (err) { return err; } } base = member.m_class->get_datatype(); found = true; break; } look_class = look_class->outer; } if (!found) { push_error(vformat(R"(Could not find base class "%s".)", name), p_class); return ERR_PARSE_ERROR; } } } for (int index = extends_index; index < p_class->extends.size(); index++) { if (base.kind != GDScriptParser::DataType::CLASS) { push_error(R"(Super type "%s" is not a GDScript. Cannot get nested types.)", p_class); return ERR_PARSE_ERROR; } // TODO: Extends could use identifier nodes. That way errors can be pointed out properly and it can be used here. GDScriptParser::IdentifierNode *id = parser->alloc_node(); id->name = p_class->extends[index]; reduce_identifier_from_base(id, &base); GDScriptParser::DataType id_type = id->get_datatype(); if (!id_type.is_set()) { push_error(vformat(R"(Could not find type "%s" under base "%s".)", id->name, base.to_string()), p_class); } base = id_type; } result = base; } if (!result.is_set()) { // TODO: More specific error messages. push_error(vformat(R"(Could not resolve inheritance for class "%s".)", p_class->identifier == nullptr ? "
" : p_class->identifier->name), p_class); return ERR_PARSE_ERROR; } // Check for cyclic inheritance. const GDScriptParser::ClassNode *base_class = result.class_type; while (base_class) { if (base_class->fqcn == p_class->fqcn) { push_error("Cyclic inheritance.", p_class); return ERR_PARSE_ERROR; } base_class = base_class->base_type.class_type; } p_class->base_type = result; class_type.native_type = result.native_type; p_class->set_datatype(class_type); if (p_recursive) { for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) { Error err = resolve_inheritance(p_class->members[i].m_class, true); if (err) { return err; } } } } return OK; } GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::TypeNode *p_type) { GDScriptParser::DataType result; if (p_type == nullptr) { result.kind = GDScriptParser::DataType::VARIANT; return result; } result.type_source = result.ANNOTATED_EXPLICIT; result.builtin_type = Variant::OBJECT; if (p_type->type_chain.is_empty()) { // void. result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = Variant::NIL; p_type->set_datatype(result); return result; } StringName first = p_type->type_chain[0]->name; if (first == "Variant") { result.kind = GDScriptParser::DataType::VARIANT; if (p_type->type_chain.size() > 1) { push_error(R"("Variant" type don't contain nested types.)", p_type->type_chain[1]); return GDScriptParser::DataType(); } return result; } if (first == "Object") { result.kind = GDScriptParser::DataType::NATIVE; result.native_type = "Object"; if (p_type->type_chain.size() > 1) { push_error(R"("Object" type don't contain nested types.)", p_type->type_chain[1]); return GDScriptParser::DataType(); } return result; } if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) { // Built-in types. if (p_type->type_chain.size() > 1) { push_error(R"(Built-in types don't contain nested types.)", p_type->type_chain[1]); return GDScriptParser::DataType(); } result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = GDScriptParser::get_builtin_type(first); if (result.builtin_type == Variant::ARRAY) { GDScriptParser::DataType container_type = resolve_datatype(p_type->container_type); if (container_type.kind != GDScriptParser::DataType::VARIANT) { container_type.is_meta_type = false; container_type.is_constant = false; result.set_container_element_type(container_type); } } } else if (class_exists(first)) { // Native engine classes. result.kind = GDScriptParser::DataType::NATIVE; result.native_type = first; } else if (ScriptServer::is_global_class(first)) { if (parser->script_path == ScriptServer::get_global_class_path(first)) { result = parser->head->get_datatype(); } else { Ref ref = get_parser_for(ScriptServer::get_global_class_path(first)); if (ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) { push_error(vformat(R"(Could not parse global class "%s" from "%s".)", first, ScriptServer::get_global_class_path(first)), p_type); return GDScriptParser::DataType(); } result = ref->get_parser()->head->get_datatype(); } } else if (ProjectSettings::get_singleton()->has_autoload(first) && ProjectSettings::get_singleton()->get_autoload(first).is_singleton) { const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(first); Ref ref = get_parser_for(autoload.path); if (ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) { push_error(vformat(R"(Could not parse singleton "%s" from "%s".)", first, autoload.path), p_type); return GDScriptParser::DataType(); } result = ref->get_parser()->head->get_datatype(); } else if (ClassDB::has_enum(parser->current_class->base_type.native_type, first)) { // Native enum in current class. result = make_native_enum_type(parser->current_class->base_type.native_type, first); } else { // Classes in current scope. GDScriptParser::ClassNode *script_class = parser->current_class; bool found = false; while (!found && script_class != nullptr) { if (script_class->identifier && script_class->identifier->name == first) { result = script_class->get_datatype(); found = true; break; } if (script_class->members_indices.has(first)) { GDScriptParser::ClassNode::Member member = script_class->members[script_class->members_indices[first]]; switch (member.type) { case GDScriptParser::ClassNode::Member::CLASS: result = member.m_class->get_datatype(); found = true; break; case GDScriptParser::ClassNode::Member::ENUM: result = member.m_enum->get_datatype(); found = true; break; case GDScriptParser::ClassNode::Member::CONSTANT: if (member.constant->get_datatype().is_meta_type) { result = member.constant->get_datatype(); result.is_meta_type = false; found = true; break; } else if (Ref