diff options
Diffstat (limited to 'modules/gdscript/gdscript_analyzer.cpp')
-rw-r--r-- | modules/gdscript/gdscript_analyzer.cpp | 599 |
1 files changed, 370 insertions, 229 deletions
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 9fa518ca0b..81e50d6b79 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -32,6 +32,7 @@ #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" @@ -39,6 +40,7 @@ #include "core/templates/hash_map.h" #include "gdscript.h" #include "gdscript_utility_functions.h" +#include "scene/resources/packed_scene.h" static MethodInfo info_from_utility_func(const StringName &p_function) { ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo()); @@ -132,7 +134,7 @@ static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) { return type; } -bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class) { +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]; @@ -145,6 +147,9 @@ bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName 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; @@ -160,6 +165,9 @@ bool GDScriptAnalyzer::has_member_name_conflict_in_native_type(const StringName 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; } @@ -187,14 +195,18 @@ Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::C 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); + 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( @@ -207,22 +219,28 @@ Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::C return OK; } +void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list) { + if (p_list->find(p_node) != nullptr) { + return; + } + p_list->push_back(p_node); + + // 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_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)) { @@ -260,21 +278,21 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, 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(); + p_class->extends_path = class_type.script_path.get_base_dir().path_join(p_class->extends_path).simplify_path(); } - Ref<GDScriptParserRef> parser = get_parser_for(p_class->extends_path); - if (parser.is_null()) { + Ref<GDScriptParserRef> 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 = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + Error err = ext_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(); + 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); @@ -289,18 +307,18 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, if (base_path == parser->script_path) { base = parser->head->get_datatype(); } else { - Ref<GDScriptParserRef> parser = get_parser_for(base_path); - if (parser.is_null()) { + Ref<GDScriptParserRef> 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 = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + Error err = base_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(); + 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); @@ -309,13 +327,13 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return ERR_PARSE_ERROR; } - Ref<GDScriptParserRef> parser = get_parser_for(info.path); - if (parser.is_null()) { + Ref<GDScriptParserRef> 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 = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + Error err = info_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; @@ -325,9 +343,10 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, base.native_type = name; } else { // Look for other classes in script. - GDScriptParser::ClassNode *look_class = p_class; bool found = false; - while (look_class != nullptr) { + List<GDScriptParser::ClassNode *> 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_inheritance(look_class, false); @@ -351,7 +370,6 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, found = true; break; } - look_class = look_class->outer; } if (!found) { @@ -484,12 +502,23 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type if (parser->script_path == ScriptServer::get_global_class_path(first)) { result = parser->head->get_datatype(); } else { - Ref<GDScriptParserRef> ref = get_parser_for(ScriptServer::get_global_class_path(first)); - if (!ref.is_valid() || 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(); + String path = ScriptServer::get_global_class_path(first); + String ext = path.get_extension(); + if (ext == GDScriptLanguage::get_singleton()->get_extension()) { + Ref<GDScriptParserRef> ref = get_parser_for(path); + if (!ref.is_valid() || 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 { + 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; } - 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); @@ -504,12 +533,11 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type 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) { + List<GDScriptParser::ClassNode *> script_classes; + get_class_node_current_scope_classes(parser->current_class, &script_classes); + for (GDScriptParser::ClassNode *script_class : script_classes) { if (script_class->identifier && script_class->identifier->name == first) { result = script_class->get_datatype(); - found = true; break; } if (script_class->members_indices.has(first)) { @@ -517,35 +545,33 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type 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<Script>(member.constant->initializer->reduced_value).is_valid()) { Ref<GDScript> gdscript = member.constant->initializer->reduced_value; if (gdscript.is_valid()) { - Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_path()); + Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_script_path()); if (ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) { - push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_path()), p_type); + push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), p_type); return GDScriptParser::DataType(); } result = ref->get_parser()->head->get_datatype(); result.is_meta_type = false; } else { - Ref<GDScript> script = member.constant->initializer->reduced_value; + Ref<Script> script = member.constant->initializer->reduced_value; result.kind = GDScriptParser::DataType::SCRIPT; result.builtin_type = Variant::OBJECT; result.script_type = script; result.script_path = script->get_path(); result.native_type = script->get_instance_base_type(); + result.is_meta_type = false; } break; } @@ -555,7 +581,6 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return GDScriptParser::DataType(); } } - script_class = script_class->outer; } } if (!result.is_set()) { @@ -655,43 +680,43 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } else { ERR_PRINT("Parser bug (please report): tried to assign unset node without an identifier."); } - } else { - if (member.variable->datatype_specifier != nullptr) { - datatype = specified_type; + } - if (member.variable->initializer != nullptr) { - if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true, member.variable->initializer)) { - // Try reverse test since it can be a masked subtype. - if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true, member.variable->initializer)) { - push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer); - } else { - // TODO: Add warning. - mark_node_unsafe(member.variable->initializer); - member.variable->use_conversion_assign = true; - } - } else if (datatype.builtin_type == Variant::INT && member.variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { -#ifdef DEBUG_ENABLED - parser->push_warning(member.variable->initializer, GDScriptWarning::NARROWING_CONVERSION); -#endif - } - if (member.variable->initializer->get_datatype().is_variant()) { - // TODO: Warn unsafe assign. + if (member.variable->datatype_specifier != nullptr) { + datatype = specified_type; + + if (member.variable->initializer != nullptr) { + if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true, member.variable->initializer)) { + // Try reverse test since it can be a masked subtype. + if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true, member.variable->initializer)) { + push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer); + } else { + // TODO: Add warning. mark_node_unsafe(member.variable->initializer); member.variable->use_conversion_assign = true; } + } else if (datatype.builtin_type == Variant::INT && member.variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { +#ifdef DEBUG_ENABLED + parser->push_warning(member.variable->initializer, GDScriptWarning::NARROWING_CONVERSION); +#endif } - } else if (member.variable->infer_datatype) { - if (member.variable->initializer == nullptr) { - push_error(vformat(R"(Cannot infer the type of "%s" variable because there's no default value.)", member.variable->identifier->name), member.variable->identifier); - } else if (!datatype.is_set() || datatype.has_no_type()) { - push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value doesn't have a set type.)", member.variable->identifier->name), member.variable->initializer); - } else if (datatype.is_variant()) { - push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is Variant. Use explicit "Variant" type if this is intended.)", member.variable->identifier->name), member.variable->initializer); - } else if (datatype.builtin_type == Variant::NIL) { - push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is "null".)", member.variable->identifier->name), member.variable->initializer); + if (member.variable->initializer->get_datatype().is_variant()) { + // TODO: Warn unsafe assign. + mark_node_unsafe(member.variable->initializer); + member.variable->use_conversion_assign = true; } - datatype.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; } + } else if (member.variable->infer_datatype) { + if (member.variable->initializer == nullptr) { + push_error(vformat(R"(Cannot infer the type of "%s" variable because there's no default value.)", member.variable->identifier->name), member.variable->identifier); + } else if (!datatype.is_set() || datatype.has_no_type()) { + push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value doesn't have a set type.)", member.variable->identifier->name), member.variable->initializer); + } else if (datatype.is_variant()) { + push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is Variant. Use explicit "Variant" type if this is intended.)", member.variable->identifier->name), member.variable->initializer); + } else if (datatype.builtin_type == Variant::NIL) { + push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is "null".)", member.variable->identifier->name), member.variable->initializer); + } + datatype.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; } datatype.is_constant = false; @@ -860,6 +885,9 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas case GDScriptParser::ClassNode::Member::CLASS: check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class); break; + case GDScriptParser::ClassNode::Member::GROUP: + // No-op, but needed to silence warnings. + break; case GDScriptParser::ClassNode::Member::UNDEFINED: ERR_PRINT("Trying to resolve undefined member."); break; @@ -970,21 +998,26 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { if (getter_function == nullptr) { push_error(vformat(R"(Getter "%s" not found.)", member.variable->getter_pointer->name), member.variable); - - } else if (getter_function->parameters.size() != 0 || getter_function->datatype.has_no_type()) { - push_error(vformat(R"(Function "%s" cannot be used as getter because of its signature.)", getter_function->identifier->name), member.variable); - - } else if (!is_type_compatible(member.variable->datatype, getter_function->datatype, true)) { - push_error(vformat(R"(Function with return type "%s" cannot be used as getter for a property of type "%s".)", getter_function->datatype.to_string(), member.variable->datatype.to_string()), member.variable); - } else { - has_valid_getter = true; + GDScriptParser::DataType return_datatype = getter_function->datatype; + if (getter_function->return_type != nullptr) { + return_datatype = getter_function->return_type->datatype; + return_datatype.is_meta_type = false; + } + if (getter_function->parameters.size() != 0 || return_datatype.has_no_type()) { + push_error(vformat(R"(Function "%s" cannot be used as getter because of its signature.)", getter_function->identifier->name), member.variable); + } else if (!is_type_compatible(member.variable->datatype, return_datatype, true)) { + push_error(vformat(R"(Function with return type "%s" cannot be used as getter for a property of type "%s".)", return_datatype.to_string(), member.variable->datatype.to_string()), member.variable); + + } else { + has_valid_getter = true; #ifdef DEBUG_ENABLED - if (member.variable->datatype.builtin_type == Variant::INT && getter_function->datatype.builtin_type == Variant::FLOAT) { - parser->push_warning(member.variable, GDScriptWarning::NARROWING_CONVERSION); - } + if (member.variable->datatype.builtin_type == Variant::INT && return_datatype.builtin_type == Variant::FLOAT) { + parser->push_warning(member.variable, GDScriptWarning::NARROWING_CONVERSION); + } #endif + } } } @@ -1026,7 +1059,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { } } -void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) { +void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root) { ERR_FAIL_COND_MSG(p_node == nullptr, "Trying to resolve type of a null node."); switch (p_node->type) { @@ -1099,7 +1132,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) { case GDScriptParser::Node::SUBSCRIPT: case GDScriptParser::Node::TERNARY_OPERATOR: case GDScriptParser::Node::UNARY_OPERATOR: - reduce_expression(static_cast<GDScriptParser::ExpressionNode *>(p_node), true); + reduce_expression(static_cast<GDScriptParser::ExpressionNode *>(p_node), p_is_root); break; case GDScriptParser::Node::BREAK: case GDScriptParser::Node::BREAKPOINT: @@ -1143,6 +1176,8 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * if (p_function->parameters[i]->default_value->is_constant) { p_function->default_arg_values.push_back(p_function->parameters[i]->default_value->reduced_value); + } else { + p_function->default_arg_values.push_back(Variant()); // Prevent shift. } } #endif // TOOLS_ENABLED @@ -1195,11 +1230,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * if (!valid) { // Compute parent signature as a string to show in the error message. - String parent_signature = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant"; - if (parent_signature == "null") { - parent_signature = "void"; - } - parent_signature += " " + p_function->identifier->name.operator String() + "("; + String parent_signature = p_function->identifier->name.operator String() + "("; int j = 0; for (const GDScriptParser::DataType &par_type : parameters_types) { if (j > 0) { @@ -1216,7 +1247,15 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * j++; } - parent_signature += ")"; + parent_signature += ") -> "; + + const String return_type = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant"; + if (return_type == "null") { + parent_signature += "void"; + } else { + parent_signature += return_type; + } + push_error(vformat(R"(The function signature doesn't match the parent. Parent signature is "%s".)", parent_signature), p_function); } } @@ -1362,7 +1401,7 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { if (all_is_constant) { switch (args.size()) { case 1: - reduced = args[0]; + reduced = (int32_t)args[0]; break; case 2: reduced = Vector2i(args[0], args[1]); @@ -1396,7 +1435,7 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { variable_type.builtin_type = Variant::INT; // Can this ever be a float or something else? p_for->variable->set_datatype(variable_type); } else if (p_for->list) { - resolve_node(p_for->list); + resolve_node(p_for->list, false); if (p_for->list->datatype.has_container_element_type()) { variable_type = p_for->list->datatype.get_container_element_type(); variable_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; @@ -1424,7 +1463,7 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { } void GDScriptAnalyzer::resolve_while(GDScriptParser::WhileNode *p_while) { - resolve_node(p_while->condition); + resolve_node(p_while->condition, false); resolve_suite(p_while->loop); p_while->set_datatype(p_while->loop->get_datatype()); @@ -1587,8 +1626,8 @@ void GDScriptAnalyzer::resolve_assert(GDScriptParser::AssertNode *p_assert) { reduce_expression(p_assert->condition); if (p_assert->message != nullptr) { reduce_expression(p_assert->message); - if (!p_assert->message->is_constant || p_assert->message->reduced_value.get_type() != Variant::STRING) { - push_error(R"(Expected constant string for assert error message.)", p_assert->message); + if (!p_assert->message->get_datatype().has_no_type() && (p_assert->message->get_datatype().kind != GDScriptParser::DataType::BUILTIN || p_assert->message->get_datatype().builtin_type != Variant::STRING)) { + push_error(R"(Expected string for assert error message.)", p_assert->message); } } @@ -1657,8 +1696,8 @@ void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_matc p_match_pattern->bind->set_datatype(result); #ifdef DEBUG_ENABLED is_shadowing(p_match_pattern->bind, "pattern bind"); - if (p_match_pattern->bind->usages == 0) { - parser->push_warning(p_match_pattern->bind, GDScriptWarning::UNASSIGNED_VARIABLE, p_match_pattern->bind->name); + if (p_match_pattern->bind->usages == 0 && !String(p_match_pattern->bind->name).begins_with("_")) { + parser->push_warning(p_match_pattern->bind, GDScriptWarning::UNUSED_VARIABLE, p_match_pattern->bind->name); } #endif break; @@ -1706,7 +1745,6 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame } else { result.type_source = GDScriptParser::DataType::INFERRED; } - result.is_constant = false; } if (p_parameter->datatype_specifier != nullptr) { @@ -1726,6 +1764,7 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is "null".)", p_parameter->identifier->name), p_parameter->default_value); } + result.is_constant = false; p_parameter->set_datatype(result); } @@ -1809,7 +1848,7 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre reduce_binary_op(static_cast<GDScriptParser::BinaryOpNode *>(p_expression)); break; case GDScriptParser::Node::CALL: - reduce_call(static_cast<GDScriptParser::CallNode *>(p_expression), p_is_root); + reduce_call(static_cast<GDScriptParser::CallNode *>(p_expression), false, p_is_root); break; case GDScriptParser::Node::CAST: reduce_cast(static_cast<GDScriptParser::CastNode *>(p_expression)); @@ -2132,7 +2171,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o GDScriptParser::DataType test_type = right_type; test_type.is_meta_type = false; - if (!is_type_compatible(test_type, p_binary_op->left_operand->get_datatype(), false)) { + if (!is_type_compatible(test_type, left_type, false)) { push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)"), p_binary_op->left_operand); p_binary_op->reduced_value = false; } else { @@ -2166,11 +2205,11 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o GDScriptParser::DataType test_type = right_type; test_type.is_meta_type = false; - if (!is_type_compatible(test_type, p_binary_op->left_operand->get_datatype(), false)) { + if (!is_type_compatible(test_type, left_type, false)) { // Test reverse as well to consider for subtypes. - if (!is_type_compatible(p_binary_op->left_operand->get_datatype(), test_type, false)) { - if (p_binary_op->left_operand->get_datatype().is_hard_type()) { - push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", p_binary_op->left_operand->get_datatype().to_string(), test_type.to_string()), p_binary_op->left_operand); + if (!is_type_compatible(left_type, test_type, false)) { + if (left_type.is_hard_type()) { + push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", left_type.to_string(), test_type.to_string()), p_binary_op->left_operand); } else { // TODO: Warning. mark_node_unsafe(p_binary_op); @@ -2278,6 +2317,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a push_error(vformat(R"(Too few arguments for %s constructor. Received %d but expected %d.)", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); break; case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: + case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST: break; // Can't happen in a builtin constructor. case Callable::CallError::CALL_OK: p_call->is_constant = true; @@ -2380,6 +2420,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: push_error(vformat(R"*(Too few arguments for "%s()" call. Expected at least %d but received %d.)*", function_name, err.expected, p_call->arguments.size()), p_call); break; + case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST: case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: break; // Can't happen in a builtin constructor. case Callable::CallError::CALL_OK: @@ -2408,9 +2449,15 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a switch (err.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { - PropertyInfo wrong_arg = function_info.arguments[err.argument]; + String expected_type_name; + if (err.argument < function_info.arguments.size()) { + expected_type_name = type_from_property(function_info.arguments[err.argument]).to_string(); + } else { + expected_type_name = Variant::get_type_name((Variant::Type)err.expected); + } + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", function_name, err.argument + 1, - type_from_property(wrong_arg).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()), + expected_type_name, p_call->arguments[err.argument]->get_datatype().to_string()), p_call->arguments[err.argument]); } break; case Callable::CallError::CALL_ERROR_INVALID_METHOD: @@ -2422,6 +2469,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: push_error(vformat(R"*(Too few arguments for "%s()" call. Expected at least %d but received %d.)*", function_name, err.expected, p_call->arguments.size()), p_call); break; + case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST: case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: break; // Can't happen in a builtin constructor. case Callable::CallError::CALL_OK: @@ -2530,6 +2578,22 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a mark_lambda_use_self(); } +#ifdef DEBUG_ENABLED + if (p_is_root && return_type.kind != GDScriptParser::DataType::UNRESOLVED && return_type.builtin_type != Variant::NIL) { + parser->push_warning(p_call, GDScriptWarning::RETURN_VALUE_DISCARDED, p_call->function_name); + } + + if (is_static && !base_type.is_meta_type && !(is_self && parser->current_function != nullptr && parser->current_function->is_static)) { + String caller_type = String(base_type.native_type); + + if (caller_type.is_empty()) { + caller_type = base_type.to_string(); + } + + parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, caller_type); + } +#endif // DEBUG_ENABLED + call_type = return_type; } else { bool found = false; @@ -2623,7 +2687,7 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { } void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) { - HashMap<Variant, GDScriptParser::ExpressionNode *, VariantHasher, VariantComparator> elements; + HashMap<Variant, GDScriptParser::ExpressionNode *, VariantHasher, StringLikeVariantComparator> elements; for (int i = 0; i < p_dictionary->elements.size(); i++) { const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; @@ -2670,31 +2734,37 @@ void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source) { GDScriptParser::DataType type; - Ref<GDScriptParserRef> ref = get_parser_for(ScriptServer::get_global_class_path(p_class_name)); - if (ref.is_null()) { - push_error(vformat(R"(Could not find script for class "%s".)", p_class_name), p_source); - type.type_source = GDScriptParser::DataType::UNDETECTED; - type.kind = GDScriptParser::DataType::VARIANT; - return type; - } + String path = ScriptServer::get_global_class_path(p_class_name); + String ext = path.get_extension(); + if (ext == GDScriptLanguage::get_singleton()->get_extension()) { + Ref<GDScriptParserRef> ref = get_parser_for(path); + if (ref.is_null()) { + push_error(vformat(R"(Could not find script for class "%s".)", p_class_name), p_source); + type.type_source = GDScriptParser::DataType::UNDETECTED; + type.kind = GDScriptParser::DataType::VARIANT; + return type; + } - Error err = ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); - if (err) { - push_error(vformat(R"(Could not resolve class "%s", because of a parser error.)", p_class_name), p_source); - type.type_source = GDScriptParser::DataType::UNDETECTED; - type.kind = GDScriptParser::DataType::VARIANT; + Error err = ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + if (err) { + push_error(vformat(R"(Could not resolve class "%s", because of a parser error.)", p_class_name), p_source); + type.type_source = GDScriptParser::DataType::UNDETECTED; + type.kind = GDScriptParser::DataType::VARIANT; + return type; + } + + return ref->get_parser()->head->get_datatype(); + } else { + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = GDScriptParser::DataType::SCRIPT; + type.builtin_type = Variant::OBJECT; + type.script_type = ResourceLoader::load(path, "Script"); + type.native_type = type.script_type->get_instance_base_type(); + type.script_path = path; + type.is_constant = true; + type.is_meta_type = true; return type; } - - type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - type.kind = GDScriptParser::DataType::CLASS; - type.builtin_type = Variant::OBJECT; - type.native_type = ScriptServer::get_global_class_native_base(p_class_name); - type.class_type = ref->get_parser()->head; - type.script_path = ref->get_parser()->script_path; - type.is_constant = true; - type.is_meta_type = true; - return type; } void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType *p_base) { @@ -2720,6 +2790,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod result.builtin_type = Variant::INT; result.native_type = base.native_type; result.enum_type = base.enum_type; + result.enum_values = base.enum_values; p_identifier->set_datatype(result); return; } else { @@ -2812,6 +2883,9 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod p_identifier->variable_source = member.variable; member.variable->usages += 1; break; + case GDScriptParser::ClassNode::Member::SIGNAL: + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_SIGNAL; + break; case GDScriptParser::ClassNode::Member::FUNCTION: resolve_function_signature(member.function); p_identifier->set_datatype(make_callable_type(member.function->info)); @@ -2828,47 +2902,49 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } // Check outer constants. // TODO: Allow outer static functions. - GDScriptParser::ClassNode *outer = base_class->outer; - while (outer != nullptr) { - if (outer->has_member(name)) { - const GDScriptParser::ClassNode::Member &member = outer->get_member(name); - switch (member.type) { - case GDScriptParser::ClassNode::Member::CONSTANT: { - // TODO: Make sure loops won't cause problem. And make special error message for those. - // For out-of-order resolution: - reduce_expression(member.constant->initializer); - p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = true; - p_identifier->reduced_value = member.constant->initializer->reduced_value; - return; - } break; - case GDScriptParser::ClassNode::Member::ENUM_VALUE: { - p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = true; - p_identifier->reduced_value = member.enum_value.value; - return; - } break; - case GDScriptParser::ClassNode::Member::ENUM: { - p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = false; - return; - } break; - case GDScriptParser::ClassNode::Member::CLASS: { - resolve_class_interface(member.m_class); - p_identifier->set_datatype(member.m_class->get_datatype()); - return; - } break; - default: - break; + if (base_class->outer != nullptr) { + List<GDScriptParser::ClassNode *> script_classes; + get_class_node_current_scope_classes(parser->current_class, &script_classes); + for (GDScriptParser::ClassNode *script_class : script_classes) { + if (script_class->has_member(name)) { + const GDScriptParser::ClassNode::Member &member = script_class->get_member(name); + switch (member.type) { + case GDScriptParser::ClassNode::Member::CONSTANT: { + // TODO: Make sure loops won't cause problem. And make special error message for those. + // For out-of-order resolution: + reduce_expression(member.constant->initializer); + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.constant->initializer->reduced_value; + return; + } break; + case GDScriptParser::ClassNode::Member::ENUM_VALUE: { + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.enum_value.value; + return; + } break; + case GDScriptParser::ClassNode::Member::ENUM: { + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = false; + return; + } break; + case GDScriptParser::ClassNode::Member::CLASS: { + resolve_class_interface(member.m_class); + p_identifier->set_datatype(member.m_class->get_datatype()); + return; + } break; + default: + break; + } } } - outer = outer->outer; } base_class = base_class->base_type.class_type; } - // Check native members. + // Check native members. No need for native class recursion because Node exposes all Object's properties. const StringName &native = base.native_type; if (class_exists(native)) { @@ -2900,7 +2976,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod return; } bool valid = false; - int int_constant = ClassDB::get_integer_constant(native, name, &valid); + int64_t int_constant = ClassDB::get_integer_constant(native, name, &valid); if (valid) { p_identifier->is_constant = true; p_identifier->reduced_value = int_constant; @@ -2956,6 +3032,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident p_identifier->reduced_value = p_identifier->constant_source->initializer->reduced_value; found_source = true; break; + case GDScriptParser::IdentifierNode::MEMBER_SIGNAL: case GDScriptParser::IdentifierNode::INHERITED_VARIABLE: mark_lambda_use_self(); break; @@ -3059,12 +3136,29 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident GDScriptParser::DataType result; result.kind = GDScriptParser::DataType::NATIVE; result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - if (autoload.path.to_lower().ends_with(GDScriptLanguage::get_singleton()->get_extension())) { - Ref<GDScriptParserRef> parser = get_parser_for(autoload.path); - if (parser.is_valid()) { - Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + if (ResourceLoader::get_resource_type(autoload.path) == "GDScript") { + Ref<GDScriptParserRef> singl_parser = get_parser_for(autoload.path); + if (singl_parser.is_valid()) { + Error err = singl_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); if (err == OK) { - result = type_from_metatype(parser->get_parser()->head->get_datatype()); + result = type_from_metatype(singl_parser->get_parser()->head->get_datatype()); + } + } + } else if (ResourceLoader::get_resource_type(autoload.path) == "PackedScene") { + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(name)) { + Variant constant = GDScriptLanguage::get_singleton()->get_named_globals_map()[name]; + Node *node = Object::cast_to<Node>(constant); + if (node != nullptr) { + Ref<GDScript> scr = node->get_script(); + if (scr.is_valid()) { + Ref<GDScriptParserRef> singl_parser = get_parser_for(scr->get_script_path()); + if (singl_parser.is_valid()) { + Error err = singl_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + if (err == OK) { + result = type_from_metatype(singl_parser->get_parser()->head->get_datatype()); + } + } + } } } } @@ -3179,16 +3273,41 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { p_preload->resolved_path = p_preload->path->reduced_value; // TODO: Save this as script dependency. if (p_preload->resolved_path.is_relative_path()) { - p_preload->resolved_path = parser->script_path.get_base_dir().plus_file(p_preload->resolved_path); + p_preload->resolved_path = parser->script_path.get_base_dir().path_join(p_preload->resolved_path); } p_preload->resolved_path = p_preload->resolved_path.simplify_path(); - if (!FileAccess::exists(p_preload->resolved_path)) { - push_error(vformat(R"(Preload file "%s" does not exist.)", p_preload->resolved_path), p_preload->path); + if (!ResourceLoader::exists(p_preload->resolved_path)) { + Ref<FileAccess> file_check = FileAccess::create(FileAccess::ACCESS_RESOURCES); + + if (file_check->file_exists(p_preload->resolved_path)) { + push_error(vformat(R"(Preload file "%s" has no resource loaders (unrecognized file extension).)", p_preload->resolved_path), p_preload->path); + } else { + push_error(vformat(R"(Preload file "%s" does not exist.)", p_preload->resolved_path), p_preload->path); + } } else { // TODO: Don't load if validating: use completion cache. - p_preload->resource = ResourceLoader::load(p_preload->resolved_path); - if (p_preload->resource.is_null()) { - push_error(vformat(R"(Could not preload resource file "%s".)", p_preload->resolved_path), p_preload->path); + + // Must load GDScript and PackedScenes separately to permit cyclic references + // as ResourceLoader::load() detect and reject 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); + p_preload->resource = res; + 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()) { + push_error(vformat(R"(Could not preload resource file "%s".)", p_preload->resolved_path), p_preload->path); + } } } } @@ -3226,21 +3345,33 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri if (p_subscript->attribute == nullptr) { return; } - if (p_subscript->base->is_constant) { + + GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); + // If base is a class metatype, use the analyzer instead. + if (p_subscript->base->is_constant && !(base_type.is_meta_type && base_type.kind == GDScriptParser::DataType::CLASS)) { // Just try to get it. bool valid = false; Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid); + + // If it's a GDScript instance, try to get the full script. Maybe it's not still completely loaded. + Ref<GDScript> gdscr = Ref<GDScript>(p_subscript->base->reduced_value); + if (!valid && gdscr.is_valid()) { + Error err = OK; + GDScriptCache::get_full_script(gdscr->get_script_path(), err); + if (err == OK) { + value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid); + } + } + if (!valid) { push_error(vformat(R"(Cannot get member "%s" from "%s".)", p_subscript->attribute->name, p_subscript->base->reduced_value), p_subscript->index); + result_type.kind = GDScriptParser::DataType::VARIANT; } else { p_subscript->is_constant = true; p_subscript->reduced_value = value; result_type = type_from_variant(value, p_subscript); } - result_type.kind = GDScriptParser::DataType::VARIANT; } else { - GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); - if (base_type.is_variant() || !base_type.is_hard_type()) { result_type.kind = GDScriptParser::DataType::VARIANT; mark_node_unsafe(p_subscript); @@ -3315,7 +3446,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::QUATERNION: case Variant::AABB: case Variant::OBJECT: - error = index_type.builtin_type != Variant::STRING; + error = index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME; break; // Expect String or number. case Variant::BASIS: @@ -3323,14 +3454,17 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::VECTOR2I: case Variant::VECTOR3: case Variant::VECTOR3I: + case Variant::VECTOR4: + case Variant::VECTOR4I: case Variant::TRANSFORM2D: case Variant::TRANSFORM3D: + case Variant::PROJECTION: error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT && - index_type.builtin_type != Variant::STRING; + index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME; break; // Expect String or int. case Variant::COLOR: - error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING; + error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME; break; // Don't support indexing, but we will check it later. case Variant::RID: @@ -3387,6 +3521,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::PACKED_INT64_ARRAY: case Variant::VECTOR2I: case Variant::VECTOR3I: + case Variant::VECTOR4I: result_type.builtin_type = Variant::INT; break; // Return float. @@ -3394,6 +3529,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::PACKED_FLOAT64_ARRAY: case Variant::VECTOR2: case Variant::VECTOR3: + case Variant::VECTOR4: case Variant::QUATERNION: result_type.builtin_type = Variant::FLOAT; break; @@ -3424,6 +3560,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri break; // Depends on the index. case Variant::TRANSFORM3D: + case Variant::PROJECTION: case Variant::PLANE: case Variant::COLOR: case Variant::DICTIONARY: @@ -3503,6 +3640,7 @@ void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternar void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) { reduce_expression(p_unary_op->operand); + GDScriptParser::DataType operand_type = p_unary_op->operand->get_datatype(); GDScriptParser::DataType result; if (p_unary_op->operand == nullptr) { @@ -3515,15 +3653,17 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) p_unary_op->is_constant = true; p_unary_op->reduced_value = Variant::evaluate(p_unary_op->variant_op, p_unary_op->operand->reduced_value, Variant()); result = type_from_variant(p_unary_op->reduced_value, p_unary_op); - } else if (p_unary_op->operand->get_datatype().is_variant()) { + } + + if (operand_type.is_variant()) { result.kind = GDScriptParser::DataType::VARIANT; mark_node_unsafe(p_unary_op); } else { bool valid = false; - result = get_operation_type(p_unary_op->variant_op, p_unary_op->operand->get_datatype(), valid, p_unary_op); + result = get_operation_type(p_unary_op->variant_op, operand_type, valid, p_unary_op); if (!valid) { - push_error(vformat(R"(Invalid operand of type "%s" for unary operator "%s".)", p_unary_op->operand->get_datatype().to_string(), Variant::get_operator_name(p_unary_op->variant_op)), p_unary_op->operand); + push_error(vformat(R"(Invalid operand of type "%s" for unary operator "%s".)", operand_type.to_string(), Variant::get_operator_name(p_unary_op->variant_op)), p_unary_op); } } @@ -3591,7 +3731,13 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va result.builtin_type = p_value.get_type(); result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; // Constant has explicit type. - if (p_value.get_type() == Variant::OBJECT) { + if (p_value.get_type() == Variant::NIL) { + // A null value is a variant, not void. + result.kind = GDScriptParser::DataType::VARIANT; + } else if (p_value.get_type() == Variant::OBJECT) { + // Object is treated as a native type, not a builtin type. + result.kind = GDScriptParser::DataType::NATIVE; + Object *obj = p_value; if (!obj) { return GDScriptParser::DataType(); @@ -3606,50 +3752,43 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va scr = obj->get_script(); } if (scr.is_valid()) { - if (scr->is_valid()) { - result.script_type = scr; - result.script_path = scr->get_path(); - Ref<GDScript> gds = scr; - if (gds.is_valid()) { - result.kind = GDScriptParser::DataType::CLASS; - // This might be an inner class, so we want to get the parser for the root. - // But still get the inner class from that tree. - GDScript *current = gds.ptr(); - List<StringName> class_chain; - while (current->_owner) { - // Push to front so it's in reverse. - class_chain.push_front(current->name); - current = current->_owner; - } - - Ref<GDScriptParserRef> ref = get_parser_for(current->get_path()); - if (ref.is_null()) { - push_error("Could not find script in path.", p_source); - GDScriptParser::DataType error_type; - error_type.kind = GDScriptParser::DataType::VARIANT; - return error_type; - } - ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + result.script_type = scr; + result.script_path = scr->get_path(); + Ref<GDScript> gds = scr; + if (gds.is_valid()) { + result.kind = GDScriptParser::DataType::CLASS; + // This might be an inner class, so we want to get the parser for the root. + // But still get the inner class from that tree. + GDScript *current = gds.ptr(); + List<StringName> class_chain; + while (current->_owner) { + // Push to front so it's in reverse. + class_chain.push_front(current->name); + current = current->_owner; + } - GDScriptParser::ClassNode *found = ref->get_parser()->head; + Ref<GDScriptParserRef> ref = get_parser_for(current->get_path()); + if (ref.is_null()) { + push_error("Could not find script in path.", p_source); + GDScriptParser::DataType error_type; + error_type.kind = GDScriptParser::DataType::VARIANT; + return error_type; + } + ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); - // It should be okay to assume this exists, since we have a complete script already. - for (const StringName &E : class_chain) { - found = found->get_member(E).m_class; - } + GDScriptParser::ClassNode *found = ref->get_parser()->head; - result.class_type = found; - result.script_path = ref->get_parser()->script_path; - } else { - result.kind = GDScriptParser::DataType::SCRIPT; + // It should be okay to assume this exists, since we have a complete script already. + for (const StringName &E : class_chain) { + found = found->get_member(E).m_class; } - result.native_type = scr->get_instance_base_type(); + + result.class_type = found; + result.script_path = ref->get_parser()->script_path; } else { - push_error(vformat(R"(Constant value uses script from "%s" which is loaded but not compiled.)", scr->get_path()), p_source); - result.kind = GDScriptParser::DataType::VARIANT; - result.type_source = GDScriptParser::DataType::UNDETECTED; - result.is_meta_type = false; + result.kind = GDScriptParser::DataType::SCRIPT; } + result.native_type = scr->get_instance_base_type(); } else { result.kind = GDScriptParser::DataType::NATIVE; if (result.native_type == GDScriptNativeClass::get_class_static()) { @@ -3795,7 +3934,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo Ref<Script> base_script = p_base_type.script_type; - while (base_script.is_valid() && base_script->is_valid()) { + while (base_script.is_valid() && base_script->has_method(function_name)) { MethodInfo info = base_script->get_method_info(function_name); if (!(info == MethodInfo())) { @@ -4048,6 +4187,8 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ if (p_target.kind == GDScriptParser::DataType::BUILTIN) { bool valid = p_source.kind == GDScriptParser::DataType::BUILTIN && p_target.builtin_type == p_source.builtin_type; + valid |= p_source.builtin_type == Variant::STRING && p_target.builtin_type == Variant::STRING_NAME; + valid |= p_source.builtin_type == Variant::STRING_NAME && p_target.builtin_type == Variant::STRING; if (!valid && p_allow_implicit_conversion) { valid = Variant::can_convert_strict(p_source.builtin_type, p_target.builtin_type); } |