diff options
Diffstat (limited to 'modules/gdscript/gdscript_analyzer.cpp')
-rw-r--r-- | modules/gdscript/gdscript_analyzer.cpp | 212 |
1 files changed, 108 insertions, 104 deletions
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 5478a46bbc..6af6460b31 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -31,6 +31,7 @@ #include "gdscript_analyzer.h" #include "gdscript.h" +#include "gdscript_utility_callable.h" #include "gdscript_utility_functions.h" #include "core/config/engine.h" @@ -360,7 +361,7 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c 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)) { + } else if (ScriptServer::is_global_class(class_name) && (!GDScript::is_canonically_equal_paths(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); @@ -424,7 +425,7 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c if (ScriptServer::is_global_class(name)) { String base_path = ScriptServer::get_global_class_path(name); - if (base_path == parser->script_path) { + if (GDScript::is_canonically_equal_paths(base_path, parser->script_path)) { base = parser->head->get_datatype(); } else { Ref<GDScriptParserRef> base_parser = get_parser_for(base_path); @@ -561,6 +562,12 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c class_type.native_type = result.native_type; p_class->set_datatype(class_type); + // Apply annotations. + for (GDScriptParser::AnnotationNode *&E : p_class->annotations) { + resolve_annotation(E); + E->apply(parser, p_class, p_class->outer); + } + parser->current_class = previous_class; return OK; @@ -697,7 +704,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type result.builtin_type = Variant::OBJECT; result.native_type = first; } else if (ScriptServer::is_global_class(first)) { - if (parser->script_path == ScriptServer::get_global_class_path(first)) { + if (GDScript::is_canonically_equal_paths(parser->script_path, ScriptServer::get_global_class_path(first))) { result = parser->head->get_datatype(); } else { String path = ScriptServer::get_global_class_path(first); @@ -845,7 +852,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return result; } -void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, StringName p_name, const GDScriptParser::Node *p_source) { +void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, const StringName &p_name, const GDScriptParser::Node *p_source) { ERR_FAIL_COND(!p_class->has_member(p_name)); resolve_class_member(p_class, p_class->members_indices[p_name], p_source); } @@ -911,7 +918,6 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, { #ifdef DEBUG_ENABLED - HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings; GDScriptParser::Node *member_node = member.get_source_node(); if (member_node && member_node->type != GDScriptParser::Node::ANNOTATION) { // Apply @warning_ignore annotations before resolving member. @@ -921,9 +927,6 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, E->apply(parser, member.variable, p_class); } } - for (GDScriptWarning::Code ignored_warning : member_node->ignored_warnings) { - parser->ignored_warnings.insert(ignored_warning); - } } #endif switch (member.type) { @@ -1060,6 +1063,13 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, enum_type.enum_values[element.identifier->name] = element.value; dictionary[String(element.identifier->name)] = element.value; + +#ifdef DEBUG_ENABLED + // Named enum identifiers do not shadow anything since you can only access them with `NamedEnum.ENUM_VALUE`. + if (member.m_enum->identifier->name == StringName()) { + is_shadowing(element.identifier, "enum member", false); + } +#endif } current_enum = prev_enum; @@ -1132,9 +1142,6 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, ERR_PRINT("Trying to resolve undefined member."); break; } -#ifdef DEBUG_ENABLED - parser->ignored_warnings = previously_ignored_warnings; -#endif } parser->current_class = previous_class; @@ -1145,11 +1152,11 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas p_source = p_class; } + if (!p_class->resolved_interface) { #ifdef DEBUG_ENABLED - bool has_static_data = p_class->has_static_data; + bool has_static_data = p_class->has_static_data; #endif - if (!p_class->resolved_interface) { if (!parser->has_class(p_class)) { String script_path = p_class->get_datatype().script_path; Ref<GDScriptParserRef> parser_ref = get_parser_for(script_path); @@ -1177,6 +1184,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas return; } + p_class->resolved_interface = true; if (resolve_class_inheritance(p_class) != OK) { @@ -1318,10 +1326,6 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co GDScriptParser::ClassNode::Member member = p_class->members[i]; if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { #ifdef DEBUG_ENABLED - HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings; - for (GDScriptWarning::Code ignored_warning : member.variable->ignored_warnings) { - parser->ignored_warnings.insert(ignored_warning); - } if (member.variable->usages == 0 && String(member.variable->identifier->name).begins_with("_")) { parser->push_warning(member.variable->identifier, GDScriptWarning::UNUSED_PRIVATE_CLASS_VARIABLE, member.variable->identifier->name); } @@ -1395,10 +1399,12 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co } } } - + } else if (member.type == GDScriptParser::ClassNode::Member::SIGNAL) { #ifdef DEBUG_ENABLED - parser->ignored_warnings = previously_ignored_warnings; -#endif // DEBUG_ENABLED + if (member.signal->usages == 0) { + parser->push_warning(member.signal->identifier, GDScriptWarning::UNUSED_SIGNAL, member.signal->identifier->name); + } +#endif } } @@ -1430,6 +1436,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root case GDScriptParser::Node::NONE: break; // Unreachable. case GDScriptParser::Node::CLASS: + // NOTE: Currently this route is never executed, `resolve_class_*()` is called directly. if (OK == resolve_class_inheritance(static_cast<GDScriptParser::ClassNode *>(p_node), true)) { resolve_class_interface(static_cast<GDScriptParser::ClassNode *>(p_node), true); resolve_class_body(static_cast<GDScriptParser::ClassNode *>(p_node), true); @@ -1583,13 +1590,6 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * } p_function->resolved_signature = true; -#ifdef DEBUG_ENABLED - HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings; - for (GDScriptWarning::Code ignored_warning : p_function->ignored_warnings) { - parser->ignored_warnings.insert(ignored_warning); - } -#endif - GDScriptParser::FunctionNode *previous_function = parser->current_function; parser->current_function = p_function; bool previous_static_context = static_context; @@ -1774,9 +1774,6 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * p_function->set_datatype(prev_datatype); } -#ifdef DEBUG_ENABLED - parser->ignored_warnings = previously_ignored_warnings; -#endif parser->current_function = previous_function; static_context = previous_static_context; } @@ -1787,13 +1784,6 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun } p_function->resolved_body = true; -#ifdef DEBUG_ENABLED - HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings; - for (GDScriptWarning::Code ignored_warning : p_function->ignored_warnings) { - parser->ignored_warnings.insert(ignored_warning); - } -#endif - GDScriptParser::FunctionNode *previous_function = parser->current_function; parser->current_function = p_function; @@ -1811,9 +1801,6 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun } } -#ifdef DEBUG_ENABLED - parser->ignored_warnings = previously_ignored_warnings; -#endif parser->current_function = previous_function; static_context = previous_static_context; } @@ -1851,23 +1838,11 @@ void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) { // Apply annotations. for (GDScriptParser::AnnotationNode *&E : stmt->annotations) { resolve_annotation(E); - E->apply(parser, stmt, nullptr); - } - -#ifdef DEBUG_ENABLED - HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings; - for (GDScriptWarning::Code ignored_warning : stmt->ignored_warnings) { - parser->ignored_warnings.insert(ignored_warning); + E->apply(parser, stmt, nullptr); // TODO: Provide `p_class`. } -#endif // DEBUG_ENABLED resolve_node(stmt); resolve_pending_lambda_bodies(); - -#ifdef DEBUG_ENABLED - parser->ignored_warnings = previously_ignored_warnings; -#endif // DEBUG_ENABLED - decide_suite_type(p_suite, stmt); } } @@ -2256,6 +2231,12 @@ void GDScriptAnalyzer::resolve_match(GDScriptParser::MatchNode *p_match) { } void GDScriptAnalyzer::resolve_match_branch(GDScriptParser::MatchBranchNode *p_match_branch, GDScriptParser::ExpressionNode *p_match_test) { + // Apply annotations. + for (GDScriptParser::AnnotationNode *&E : p_match_branch->annotations) { + resolve_annotation(E); + E->apply(parser, p_match_branch, nullptr); // TODO: Provide `p_class`. + } + for (int i = 0; i < p_match_branch->patterns.size(); i++) { resolve_match_pattern(p_match_branch->patterns[i], p_match_test); } @@ -3359,12 +3340,8 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a parser->push_warning(p_call, GDScriptWarning::RETURN_VALUE_DISCARDED, p_call->function_name); } - if (method_flags.has_flag(METHOD_FLAG_STATIC) && !is_constructor && !base_type.is_meta_type && !(is_self && static_context)) { - String caller_type = String(base_type.native_type); - - if (caller_type.is_empty()) { - caller_type = base_type.to_string(); - } + if (method_flags.has_flag(METHOD_FLAG_STATIC) && !is_constructor && !base_type.is_meta_type && !is_self) { + String caller_type = base_type.to_string(); parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, caller_type); } @@ -3410,8 +3387,8 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a if (!found && (is_self || (base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::BUILTIN))) { String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string(); #ifdef SUGGEST_GODOT4_RENAMES - String rename_hint = String(); - if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_HINT)).booleanize()) { + String rename_hint; + if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) { const char *renamed_function_name = check_for_renamed_identifier(p_call->function_name, p_call->type); if (renamed_function_name) { rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", String(renamed_function_name) + "()"); @@ -3620,8 +3597,8 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod p_identifier->set_datatype(type_from_variant(result, p_identifier)); } else if (base.is_hard_type()) { #ifdef SUGGEST_GODOT4_RENAMES - String rename_hint = String(); - if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_HINT)).booleanize()) { + String rename_hint; + if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) { const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type); if (renamed_identifier_name) { rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name); @@ -3636,7 +3613,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod switch (base.builtin_type) { case Variant::NIL: { if (base.is_hard_type()) { - push_error(vformat(R"(Invalid get index "%s" on base Nil)", name), p_identifier); + push_error(vformat(R"(Cannot get property "%s" on a null object.)", name), p_identifier); } return; } @@ -3658,10 +3635,14 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod return; } } + if (Variant::has_builtin_method(base.builtin_type, name)) { + p_identifier->set_datatype(make_callable_type(Variant::get_builtin_method_info(base.builtin_type, name))); + return; + } if (base.is_hard_type()) { #ifdef SUGGEST_GODOT4_RENAMES - String rename_hint = String(); - if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_HINT)).booleanize()) { + String rename_hint; + if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) { const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type); if (renamed_identifier_name) { rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name); @@ -3745,6 +3726,8 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod if (is_base && !base.is_meta_type) { p_identifier->set_datatype(member.get_datatype()); p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_SIGNAL; + p_identifier->signal_source = member.signal; + member.signal->usages += 1; return; } } break; @@ -3929,6 +3912,8 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident found_source = true; break; case GDScriptParser::IdentifierNode::MEMBER_SIGNAL: + p_identifier->signal_source->usages++; + [[fallthrough]]; case GDScriptParser::IdentifierNode::INHERITED_VARIABLE: mark_lambda_use_self(); break; @@ -4113,6 +4098,19 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident return; } + if (Variant::has_utility_function(name) || GDScriptUtilityFunctions::function_exists(name)) { + p_identifier->is_constant = true; + p_identifier->reduced_value = Callable(memnew(GDScriptUtilityCallable(name))); + MethodInfo method_info; + if (GDScriptUtilityFunctions::function_exists(name)) { + method_info = GDScriptUtilityFunctions::get_function_info(name); + } else { + method_info = Variant::get_utility_function_info(name); + } + p_identifier->set_datatype(make_callable_type(method_info)); + return; + } + // Allow "Variant" here since it might be used for nested enums. if (can_be_builtin && name == SNAME("Variant")) { GDScriptParser::DataType variant; @@ -4125,23 +4123,18 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident } // Not found. - // Check if it's a builtin function. - if (GDScriptUtilityFunctions::function_exists(name)) { - push_error(vformat(R"(Built-in function "%s" cannot be used as an identifier.)", name), p_identifier); - } else { #ifdef SUGGEST_GODOT4_RENAMES - String rename_hint = String(); - if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_HINT)).booleanize()) { - const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type); - if (renamed_identifier_name) { - rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name); - } + String rename_hint; + if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) { + const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type); + if (renamed_identifier_name) { + rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name); } - push_error(vformat(R"(Identifier "%s" not declared in the current scope.%s)", name, rename_hint), p_identifier); + } + push_error(vformat(R"(Identifier "%s" not declared in the current scope.%s)", name, rename_hint), p_identifier); #else - push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier); + push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier); #endif // SUGGEST_GODOT4_RENAMES - } GDScriptParser::DataType dummy; dummy.kind = GDScriptParser::DataType::VARIANT; p_identifier->set_datatype(dummy); // Just so type is set to something. @@ -4206,8 +4199,8 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { } else { // TODO: Don't load if validating: use completion cache. - // Must load GDScript and PackedScenes separately to permit cyclic references - // as ResourceLoader::load() detect and reject those. + // Must load GDScript separately to permit cyclic references + // as ResourceLoader::load() detects and rejects those. if (ResourceLoader::get_resource_type(p_preload->resolved_path) == "GDScript") { Error err = OK; Ref<GDScript> res = GDScriptCache::get_shallow_script(p_preload->resolved_path, err, parser->script_path); @@ -4215,13 +4208,6 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { if (err != OK) { push_error(vformat(R"(Could not preload resource script "%s".)", p_preload->resolved_path), p_preload->path); } - } else if (ResourceLoader::get_resource_type(p_preload->resolved_path) == "PackedScene") { - Error err = OK; - Ref<PackedScene> res = GDScriptCache::get_packed_scene(p_preload->resolved_path, err, parser->script_path); - p_preload->resource = res; - if (err != OK) { - push_error(vformat(R"(Could not preload resource scene "%s".)", p_preload->resolved_path), p_preload->path); - } } else { p_preload->resource = ResourceLoader::load(p_preload->resolved_path); if (p_preload->resource.is_null()) { @@ -4904,8 +4890,19 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo } result.builtin_type = p_property.type; if (p_property.type == Variant::OBJECT) { - result.kind = GDScriptParser::DataType::NATIVE; - result.native_type = p_property.class_name == StringName() ? SNAME("Object") : p_property.class_name; + if (ScriptServer::is_global_class(p_property.class_name)) { + result.kind = GDScriptParser::DataType::SCRIPT; + result.script_path = ScriptServer::get_global_class_path(p_property.class_name); + result.native_type = ScriptServer::get_global_class_native_base(p_property.class_name); + + Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_property.class_name)); + if (scr.is_valid()) { + result.script_type = scr; + } + } else { + result.kind = GDScriptParser::DataType::NATIVE; + result.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; + } } else { result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = p_property.type; @@ -5311,8 +5308,21 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator return result; } -// TODO: Add safe/unsafe return variable (for variant cases) bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion, const GDScriptParser::Node *p_source_node) { +#ifdef DEBUG_ENABLED + if (p_source_node) { + if (p_target.kind == GDScriptParser::DataType::ENUM) { + if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) { + parser->push_warning(p_source_node, GDScriptWarning::INT_AS_ENUM_WITHOUT_CAST); + } + } + } +#endif + return check_type_compatibility(p_target, p_source, p_allow_implicit_conversion, p_source_node); +} + +// TODO: Add safe/unsafe return variable (for variant cases) +bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion, const GDScriptParser::Node *p_source_node) { // These return "true" so it doesn't affect users negatively. ERR_FAIL_COND_V_MSG(!p_target.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset target type"); ERR_FAIL_COND_V_MSG(!p_source.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset value type"); @@ -5347,11 +5357,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ if (p_target.kind == GDScriptParser::DataType::ENUM) { if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) { -#ifdef DEBUG_ENABLED - if (p_source_node) { - parser->push_warning(p_source_node, GDScriptWarning::INT_AS_ENUM_WITHOUT_CAST); - } -#endif return true; } if (p_source.kind == GDScriptParser::DataType::ENUM) { @@ -5615,21 +5620,20 @@ Error GDScriptAnalyzer::resolve_dependencies() { Error GDScriptAnalyzer::analyze() { parser->errors.clear(); - Error err = OK; - err = resolve_inheritance(); + Error err = resolve_inheritance(); if (err) { return err; } - // Apply annotations. - for (GDScriptParser::AnnotationNode *&E : parser->head->annotations) { - resolve_annotation(E); - E->apply(parser, parser->head, nullptr); - } - resolve_interface(); resolve_body(); + +#ifdef DEBUG_ENABLED + // Apply here, after all `@warning_ignore`s have been resolved and applied. + parser->apply_pending_warnings(); +#endif + if (!parser->errors.is_empty()) { return ERR_PARSE_ERROR; } |