/**************************************************************************/ /* gdscript_analyzer.cpp */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /**************************************************************************/ /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ #include "gdscript_analyzer.h" #include "core/config/engine.h" #include "core/config/project_settings.h" #include "core/core_string_names.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" #include "scene/resources/packed_scene.h" #define UNNAMED_ENUM "" #define ENUM_SEPARATOR "::" 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; } // In enum types, native_type is used to store the class (native or otherwise) that the enum belongs to. // This disambiguates between similarly named enums in base classes or outer classes static GDScriptParser::DataType make_enum_type(const StringName &p_enum_name, const String &p_base_name, const bool p_meta = false) { GDScriptParser::DataType type; type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; type.kind = GDScriptParser::DataType::ENUM; type.builtin_type = p_meta ? Variant::DICTIONARY : Variant::INT; type.enum_type = p_enum_name; type.is_constant = true; type.is_meta_type = p_meta; // For enums, native_type is only used to check compatibility in is_type_compatible() // We can set anything readable here for error messages, as long as it uniquely identifies the type of the enum type.native_type = p_base_name + ENUM_SEPARATOR + p_enum_name; return type; } static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_name, const StringName &p_native_class, const bool p_meta = true) { GDScriptParser::DataType type = make_enum_type(p_enum_name, p_native_class, p_meta); 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; } static StringName enum_get_value_name(const GDScriptParser::DataType p_type, int64_t p_val) { // Check that an enum has a given value, not key. // Make sure that implicit conversion to int64_t is sensible before calling! HashMap::ConstIterator i = p_type.enum_values.begin(); while (i) { if (i->value == p_val) { return i->key; } ++i; } return StringName(); } bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_member) { 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; } if (p_member->type != GDScriptParser::Node::FUNCTION && member->type == GDScriptParser::ClassNode::Member::FUNCTION) { 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; } if (p_member_name == CoreStringNames::get_singleton()->_script) { 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) { // TODO check outer classes for static members only 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, p_member_node)) { String parent_class_name = current_class_node->fqcn; if (current_class_node->identifier != nullptr) { parent_class_name = current_class_node->identifier->name; } push_error(vformat(R"(The member "%s" already exists in parent class %s.)", p_member_name, parent_class_name), p_member_node); return ERR_PARSE_ERROR; } current_data_type = ¤t_class_node->base_type; } // No need for native class recursion because Node exposes all Object's properties. 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; } void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List *p_list) { ERR_FAIL_NULL(p_node); ERR_FAIL_NULL(p_list); if (p_list->find(p_node) != nullptr) { return; } p_list->push_back(p_node); // TODO: Try to solve class inheritance if not yet resolving. // Prioritize node base type over its outer class if (p_node->base_type.class_type != nullptr) { get_class_node_current_scope_classes(p_node->base_type.class_type, p_list); } if (p_node->outer != nullptr) { get_class_node_current_scope_classes(p_node->outer, p_list); } } Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source) { if (p_source == nullptr && parser->has_class(p_class)) { p_source = p_class; } if (p_class->base_type.is_resolving()) { push_error(vformat(R"(Could not resolve class "%s": Cyclic reference.)", type_from_metatype(p_class->get_datatype()).to_string()), p_source); return ERR_PARSE_ERROR; } if (!p_class->base_type.has_no_type()) { // Already resolved. return OK; } if (!parser->has_class(p_class)) { String script_path = p_class->get_datatype().script_path; Ref parser_ref = get_parser_for(script_path); if (parser_ref.is_null()) { push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); return ERR_PARSE_ERROR; } Error err = parser_ref->raise_status(GDScriptParserRef::PARSED); if (err) { push_error(vformat(R"(Could not parse script "%s": %s.)", script_path, error_names[err]), p_source); return ERR_PARSE_ERROR; } ERR_FAIL_COND_V_MSG(!parser_ref->get_parser()->has_class(p_class), ERR_PARSE_ERROR, R"(Parser bug: Mismatched external parser.)"); GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer(); GDScriptParser *other_parser = parser_ref->get_parser(); int error_count = other_parser->errors.size(); other_analyzer->resolve_class_inheritance(p_class); if (other_parser->errors.size() > error_count) { push_error(vformat(R"(Could not resolve inheritance for class "%s".)", p_class->fqcn), p_source); return ERR_PARSE_ERROR; } return OK; } GDScriptParser::ClassNode *previous_class = parser->current_class; parser->current_class = p_class; if (p_class->identifier) { StringName class_name = p_class->identifier->name; if (GDScriptParser::get_builtin_type(class_name) < Variant::VARIANT_MAX) { push_error(vformat(R"(Class "%s" hides a built-in type.)", class_name), p_class->identifier); } else 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 resolving_datatype; resolving_datatype.kind = GDScriptParser::DataType::RESOLVING; p_class->base_type = resolving_datatype; // 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); GDScriptParser::DataType result; if (!p_class->extends_used) { result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; result.kind = GDScriptParser::DataType::NATIVE; result.native_type = SNAME("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().path_join(p_class->extends_path).simplify_path(); } Ref ext_parser = get_parser_for(p_class->extends_path); if (ext_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 = ext_parser->raise_status(GDScriptParserRef::INHERITANCE_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 = ext_parser->get_parser()->head->get_datatype(); } else { if (p_class->extends.is_empty()) { push_error("Could not resolve an empty super class path.", p_class); 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 base_parser = get_parser_for(base_path); if (base_parser.is_null()) { push_error(vformat(R"(Could not resolve super class "%s".)", name), p_class); return ERR_PARSE_ERROR; } Error err = base_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); return err; } base = 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() != GDScriptLanguage::get_singleton()->get_extension()) { push_error(vformat(R"(Singleton %s is not a GDScript.)", info.name), p_class); return ERR_PARSE_ERROR; } Ref info_parser = get_parser_for(info.path); if (info_parser.is_null()) { push_error(vformat(R"(Could not parse singleton from "%s".)", info.path), p_class); return ERR_PARSE_ERROR; } Error err = info_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); return err; } base = info_parser->get_parser()->head->get_datatype(); } 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. bool found = false; List script_classes; get_class_node_current_scope_classes(p_class, &script_classes); for (GDScriptParser::ClassNode *look_class : script_classes) { if (look_class->identifier && look_class->identifier->name == name) { if (!look_class->get_datatype().is_set()) { Error err = resolve_class_inheritance(look_class, p_class); if (err) { return err; } } base = look_class->get_datatype(); found = true; break; } if (look_class->has_member(name)) { resolve_class_member(look_class, name, p_class); base = look_class->get_member(name).get_datatype(); found = true; break; } } 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() || result.has_no_type()) { // 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); parser->current_class = previous_class; return OK; } Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) { Error err = resolve_class_inheritance(p_class); if (err) { return err; } if (p_recursive) { for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) { err = resolve_class_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 bad_type; bad_type.kind = GDScriptParser::DataType::VARIANT; bad_type.type_source = GDScriptParser::DataType::INFERRED; if (p_type == nullptr) { return bad_type; } if (p_type->get_datatype().is_resolving()) { push_error(R"(Could not resolve datatype: Cyclic reference.)", p_type); return bad_type; } if (!p_type->get_datatype().has_no_type()) { return p_type->get_datatype(); } GDScriptParser::DataType resolving_datatype; resolving_datatype.kind = GDScriptParser::DataType::RESOLVING; p_type->set_datatype(resolving_datatype); GDScriptParser::DataType result; result.type_source = GDScriptParser::DataType::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 == SNAME("Variant")) { if (p_type->type_chain.size() > 1) { // TODO: Variant does actually have a nested Type though. push_error(R"(Variant doesn't contain nested types.)", p_type->type_chain[1]); return bad_type; } result.kind = GDScriptParser::DataType::VARIANT; } else if (first == SNAME("Object")) { // Object is treated like a native type, not a built-in. result.kind = GDScriptParser::DataType::NATIVE; result.native_type = SNAME("Object"); } else 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 bad_type; } 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 { String path = ScriptServer::get_global_class_path(first); String ext = path.get_extension(); if (ext == GDScriptLanguage::get_singleton()->get_extension()) { Ref ref = get_parser_for(path); if (!ref.is_valid() || ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) { push_error(vformat(R"(Could not parse global class "%s" from "%s".)", first, ScriptServer::get_global_class_path(first)), p_type); return bad_type; } result = ref->get_parser()->head->get_datatype(); } else { result.kind = GDScriptParser::DataType::SCRIPT; result.script_type = ResourceLoader::load(path, "Script"); result.native_type = result.script_type->get_instance_base_type(); result.script_path = path; result.is_constant = true; result.is_meta_type = false; } } } 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::INHERITANCE_SOLVED) != OK) { push_error(vformat(R"(Could not parse singleton "%s" from "%s".)", first, autoload.path), p_type); return bad_type; } 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(first, parser->current_class->base_type.native_type); } else { // Classes in current scope. List script_classes; bool found = false; get_class_node_current_scope_classes(parser->current_class, &script_classes); for (GDScriptParser::ClassNode *script_class : script_classes) { if (found) { break; } if (script_class->identifier && script_class->identifier->name == first) { result = script_class->get_datatype(); break; } if (script_class->members_indices.has(first)) { resolve_class_member(script_class, first, p_type); GDScriptParser::ClassNode::Member member = script_class->get_member(first); switch (member.type) { case GDScriptParser::ClassNode::Member::CLASS: result = member.get_datatype(); found = true; break; case GDScriptParser::ClassNode::Member::ENUM: result = member.get_datatype(); found = true; break; case GDScriptParser::ClassNode::Member::CONSTANT: if (member.get_datatype().is_meta_type) { result = member.get_datatype(); result.is_meta_type = false; found = true; break; } else if (Ref