diff options
Diffstat (limited to 'modules/gdscript/gdscript_analyzer.cpp')
-rw-r--r-- | modules/gdscript/gdscript_analyzer.cpp | 1039 |
1 files changed, 716 insertions, 323 deletions
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index e3d3c44dd1..7026d131e3 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -248,7 +248,7 @@ Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_me return ERR_PARSE_ERROR; } - if (GDScriptParser::get_builtin_type(p_member_name) != Variant::VARIANT_MAX) { + 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; } @@ -386,6 +386,7 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c if (!p_class->extends_used) { result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; result.kind = GDScriptParser::DataType::NATIVE; + result.builtin_type = Variant::OBJECT; result.native_type = SNAME("RefCounted"); } else { result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; @@ -459,7 +460,12 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c } base = info_parser->get_parser()->head->get_datatype(); } else if (class_exists(name)) { + if (Engine::get_singleton()->has_singleton(name)) { + push_error(vformat(R"(Cannot inherit native class "%s" because it is an engine singleton.)", name), id); + return ERR_PARSE_ERROR; + } base.kind = GDScriptParser::DataType::NATIVE; + base.builtin_type = Variant::OBJECT; base.native_type = name; } else { // Look for other classes in script. @@ -613,144 +619,178 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return result; } - StringName first = p_type->type_chain[0]->name; + const GDScriptParser::IdentifierNode *first_id = p_type->type_chain[0]; + StringName first = first_id->name; + bool type_found = false; - if (first == SNAME("Variant")) { - if (p_type->type_chain.size() == 2) { - // May be nested enum. - StringName enum_name = p_type->type_chain[1]->name; - StringName qualified_name = String(first) + ENUM_SEPARATOR + String(p_type->type_chain[1]->name); - if (CoreConstants::is_global_enum(qualified_name)) { - result = make_global_enum_type(enum_name, first, true); - return result; + if (first_id->suite && first_id->suite->has_local(first)) { + const GDScriptParser::SuiteNode::Local &local = first_id->suite->get_local(first); + if (local.type == GDScriptParser::SuiteNode::Local::CONSTANT) { + result = local.get_datatype(); + if (!result.is_set()) { + // Don't try to resolve it as the constant can be declared below. + push_error(vformat(R"(Local constant "%s" is not resolved at this point.)", first), first_id); + return bad_type; + } + if (result.is_meta_type) { + type_found = true; + } else if (Ref<Script>(local.constant->initializer->reduced_value).is_valid()) { + Ref<GDScript> gdscript = local.constant->initializer->reduced_value; + if (gdscript.is_valid()) { + Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_script_path()); + if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) { + push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), first_id); + return bad_type; + } + result = ref->get_parser()->head->get_datatype(); + } else { + result = make_script_meta_type(local.constant->initializer->reduced_value); + } + type_found = true; } else { - push_error(vformat(R"(Name "%s" is not a nested type of "Variant".)", enum_name), p_type->type_chain[1]); + push_error(vformat(R"(Local constant "%s" is not a valid type.)", first), first_id); return bad_type; } - } else if (p_type->type_chain.size() > 2) { - push_error(R"(Variant only contains enum types, which do not have nested types.)", p_type->type_chain[2]); - 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.builtin_type = Variant::OBJECT; - 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]); + } else { + push_error(vformat(R"(Local %s "%s" cannot be used as a type.)", local.get_name(), first), first_id); 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 = type_from_metatype(resolve_datatype(p_type->container_type)); - if (container_type.kind != GDScriptParser::DataType::VARIANT) { - 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.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)) { - 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<GDScriptParserRef> 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); + if (!type_found) { + if (first == SNAME("Variant")) { + if (p_type->type_chain.size() == 2) { + // May be nested enum. + StringName enum_name = p_type->type_chain[1]->name; + StringName qualified_name = String(first) + ENUM_SEPARATOR + String(p_type->type_chain[1]->name); + if (CoreConstants::is_global_enum(qualified_name)) { + result = make_global_enum_type(enum_name, first, true); + return result; + } else { + push_error(vformat(R"(Name "%s" is not a nested type of "Variant".)", enum_name), p_type->type_chain[1]); return bad_type; } - result = ref->get_parser()->head->get_datatype(); - } else { - result = make_script_meta_type(ResourceLoader::load(path, "Script")); + } else if (p_type->type_chain.size() > 2) { + push_error(R"(Variant only contains enum types, which do not have nested types.)", p_type->type_chain[2]); + return bad_type; } - } - } 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<GDScriptParserRef> ref = get_parser_for(autoload.path); - if (ref.is_null()) { - push_error(vformat(R"(The referenced autoload "%s" (from "%s") could not be loaded.)", first, autoload.path), p_type); - return bad_type; - } - 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 if (CoreConstants::is_global_enum(first)) { - if (p_type->type_chain.size() > 1) { - push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[1]); - return bad_type; - } - result = make_global_enum_type(first, StringName()); - } else { - // Classes in current scope. - List<GDScriptParser::ClassNode *> 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; + result.kind = GDScriptParser::DataType::VARIANT; + } 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 (script_class->identifier && script_class->identifier->name == first) { - result = script_class->get_datatype(); - break; + if (result.builtin_type == Variant::ARRAY) { + GDScriptParser::DataType container_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(0))); + if (container_type.kind != GDScriptParser::DataType::VARIANT) { + container_type.is_constant = false; + result.set_container_element_type(0, container_type); + } + } + } else if (class_exists(first)) { + // Native engine classes. + result.kind = GDScriptParser::DataType::NATIVE; + 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)) { + 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<GDScriptParserRef> 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 = make_script_meta_type(ResourceLoader::load(path, "Script")); + } } - if (script_class->members_indices.has(first)) { - resolve_class_member(script_class, first, p_type); + } 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<GDScriptParserRef> ref = get_parser_for(autoload.path); + if (ref.is_null()) { + push_error(vformat(R"(The referenced autoload "%s" (from "%s") could not be loaded.)", first, autoload.path), p_type); + return bad_type; + } + 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 if (CoreConstants::is_global_enum(first)) { + if (p_type->type_chain.size() > 1) { + push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[1]); + return bad_type; + } + result = make_global_enum_type(first, StringName()); + } else { + // Classes in current scope. + List<GDScriptParser::ClassNode *> 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; + } - 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) { + 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; - } 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_script_path()); - if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) { - push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), p_type); - return bad_type; - } - result = ref->get_parser()->head->get_datatype(); - } else { - result = make_script_meta_type(member.constant->initializer->reduced_value); - } + case GDScriptParser::ClassNode::Member::ENUM: + result = member.get_datatype(); found = true; break; - } - [[fallthrough]]; - default: - push_error(vformat(R"("%s" is a %s but does not contain a type.)", first, member.get_type_name()), p_type); - return bad_type; + case GDScriptParser::ClassNode::Member::CONSTANT: + if (member.get_datatype().is_meta_type) { + result = member.get_datatype(); + 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_script_path()); + if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) { + push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), p_type); + return bad_type; + } + result = ref->get_parser()->head->get_datatype(); + } else { + result = make_script_meta_type(member.constant->initializer->reduced_value); + } + found = true; + break; + } + [[fallthrough]]; + default: + push_error(vformat(R"("%s" is a %s but does not contain a type.)", first, member.get_type_name()), p_type); + return bad_type; + } } } } } + if (!result.is_set()) { push_error(vformat(R"(Could not find type "%s" in the current scope.)", first), p_type); return bad_type; @@ -789,15 +829,23 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } } - if (result.builtin_type != Variant::ARRAY && p_type->container_type != nullptr) { - push_error("Only arrays can specify the collection element type.", p_type); + if (!p_type->container_types.is_empty()) { + if (result.builtin_type == Variant::ARRAY) { + if (p_type->container_types.size() != 1) { + push_error("Arrays require exactly one collection element type.", p_type); + return bad_type; + } + } else { + push_error("Only arrays can specify collection element types.", p_type); + return bad_type; + } } p_type->set_datatype(result); 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); } @@ -870,7 +918,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, for (GDScriptParser::AnnotationNode *&E : member_node->annotations) { if (E->name == SNAME("@warning_ignore")) { resolve_annotation(E); - E->apply(parser, member.variable); + E->apply(parser, member.variable, p_class); } } for (GDScriptWarning::Code ignored_warning : member_node->ignored_warnings) { @@ -882,18 +930,23 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, case GDScriptParser::ClassNode::Member::VARIABLE: { bool previous_static_context = static_context; static_context = member.variable->is_static; + check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable); + member.variable->set_datatype(resolving_datatype); resolve_variable(member.variable, false); + resolve_pending_lambda_bodies(); // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.variable->annotations) { if (E->name != SNAME("@warning_ignore")) { resolve_annotation(E); - E->apply(parser, member.variable); + E->apply(parser, member.variable, p_class); } } + static_context = previous_static_context; + #ifdef DEBUG_ENABLED if (member.variable->exported && member.variable->onready) { parser->push_warning(member.variable, GDScriptWarning::ONREADY_WITH_EXPORT); @@ -940,7 +993,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.constant->annotations) { resolve_annotation(E); - E->apply(parser, member.constant); + E->apply(parser, member.constant, p_class); } } break; case GDScriptParser::ClassNode::Member::SIGNAL: { @@ -956,15 +1009,21 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, GDScriptParser::ParameterNode *param = member.signal->parameters[j]; GDScriptParser::DataType param_type = type_from_metatype(resolve_datatype(param->datatype_specifier)); param->set_datatype(param_type); - mi.arguments.push_back(PropertyInfo(param_type.builtin_type, param->identifier->name)); - // TODO: add signal parameter default values +#ifdef DEBUG_ENABLED + if (param->datatype_specifier == nullptr) { + parser->push_warning(param, GDScriptWarning::UNTYPED_DECLARATION, "Parameter", param->identifier->name); + } +#endif + mi.arguments.push_back(param_type.to_property_info(param->identifier->name)); + // Signals do not support parameter default values. } member.signal->set_datatype(make_signal_type(mi)); + member.signal->method_info = mi; // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.signal->annotations) { resolve_annotation(E); - E->apply(parser, member.signal); + E->apply(parser, member.signal, p_class); } } break; case GDScriptParser::ClassNode::Member::ENUM: { @@ -1012,13 +1071,13 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.m_enum->annotations) { resolve_annotation(E); - E->apply(parser, member.m_enum); + E->apply(parser, member.m_enum, p_class); } } break; case GDScriptParser::ClassNode::Member::FUNCTION: for (GDScriptParser::AnnotationNode *&E : member.function->annotations) { resolve_annotation(E); - E->apply(parser, member.function); + E->apply(parser, member.function, p_class); } resolve_function_signature(member.function, p_source); break; @@ -1228,23 +1287,21 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.function->annotations) { resolve_annotation(E); - E->apply(parser, member.function); + E->apply(parser, member.function, p_class); } resolve_function_body(member.function); } else if (member.type == GDScriptParser::ClassNode::Member::VARIABLE && member.variable->property != GDScriptParser::VariableNode::PROP_NONE) { if (member.variable->property == GDScriptParser::VariableNode::PROP_INLINE) { if (member.variable->getter != nullptr) { - member.variable->getter->set_datatype(member.variable->datatype); + member.variable->getter->return_type = member.variable->datatype_specifier; + member.variable->getter->set_datatype(member.get_datatype()); resolve_function_body(member.variable->getter); } if (member.variable->setter != nullptr) { - resolve_function_signature(member.variable->setter); - - if (member.variable->setter->parameters.size() > 0) { - member.variable->setter->parameters[0]->datatype_specifier = member.variable->datatype_specifier; - member.variable->setter->parameters[0]->set_datatype(member.get_datatype()); - } + ERR_CONTINUE(member.variable->setter->parameters.is_empty()); + member.variable->setter->parameters[0]->datatype_specifier = member.variable->datatype_specifier; + member.variable->setter->parameters[0]->set_datatype(member.get_datatype()); resolve_function_body(member.variable->setter); } @@ -1252,7 +1309,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co } else if (member.type == GDScriptParser::ClassNode::Member::GROUP) { // Apply annotation (`@export_{category,group,subgroup}`). resolve_annotation(member.annotation); - member.annotation->apply(parser, nullptr); + member.annotation->apply(parser, nullptr, p_class); } } @@ -1345,6 +1402,11 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co } } + if (!pending_body_resolution_lambdas.is_empty()) { + ERR_PRINT("GDScript bug (please report): Not all pending lambda bodies were resolved in time."); + resolve_pending_lambda_bodies(); + } + parser->current_class = previous_class; } @@ -1362,7 +1424,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, bo } 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."); + ERR_FAIL_NULL_MSG(p_node, "Trying to resolve type of a null node."); switch (p_node->type) { case GDScriptParser::Node::NONE: @@ -1531,7 +1593,13 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * GDScriptParser::FunctionNode *previous_function = parser->current_function; parser->current_function = p_function; bool previous_static_context = static_context; - static_context = p_function->is_static; + if (p_is_lambda) { + // For lambdas this is determined from the context, the `static` keyword is not allowed. + p_function->is_static = static_context; + } else { + // For normal functions, this is determined in the parser by the `static` keyword. + static_context = p_function->is_static; + } GDScriptParser::DataType prev_datatype = p_function->get_datatype(); @@ -1543,21 +1611,26 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * int default_value_count = 0; #endif // TOOLS_ENABLED +#ifdef DEBUG_ENABLED + String function_visible_name = function_name; + if (function_name == StringName()) { + function_visible_name = p_is_lambda ? "<anonymous lambda>" : "<unknown function>"; + } +#endif + for (int i = 0; i < p_function->parameters.size(); i++) { resolve_parameter(p_function->parameters[i]); #ifdef DEBUG_ENABLED if (p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_")) { - String visible_name = function_name; - if (function_name == StringName()) { - visible_name = p_is_lambda ? "<anonymous lambda>" : "<unknown function>"; - } - parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, visible_name, p_function->parameters[i]->identifier->name); + parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, function_visible_name, p_function->parameters[i]->identifier->name); } - is_shadowing(p_function->parameters[i]->identifier, "function parameter"); + is_shadowing(p_function->parameters[i]->identifier, "function parameter", true); #endif // DEBUG_ENABLED -#ifdef TOOLS_ENABLED + if (p_function->parameters[i]->initializer) { +#ifdef TOOLS_ENABLED default_value_count++; +#endif // TOOLS_ENABLED if (p_function->parameters[i]->initializer->is_constant) { p_function->default_arg_values.push_back(p_function->parameters[i]->initializer->reduced_value); @@ -1565,7 +1638,6 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * p_function->default_arg_values.push_back(Variant()); // Prevent shift. } } -#endif // TOOLS_ENABLED } if (!p_is_lambda && function_name == GDScriptLanguage::get_singleton()->strings._init) { @@ -1615,15 +1687,42 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * StringName native_base; if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, method_flags, &native_base)) { bool valid = p_function->is_static == method_flags.has_flag(METHOD_FLAG_STATIC); - valid = valid && parent_return_type == p_function->get_datatype(); + + if (p_function->return_type != nullptr) { + // Check return type covariance. + GDScriptParser::DataType return_type = p_function->get_datatype(); + if (return_type.is_variant()) { + // `is_type_compatible()` returns `true` if one of the types is `Variant`. + // Don't allow an explicitly specified `Variant` if the parent return type is narrower. + valid = valid && parent_return_type.is_variant(); + } else if (return_type.kind == GDScriptParser::DataType::BUILTIN && return_type.builtin_type == Variant::NIL) { + // `is_type_compatible()` returns `true` if target is an `Object` and source is `null`. + // Don't allow `void` if the parent return type is a hard non-`void` type. + if (parent_return_type.is_hard_type() && !(parent_return_type.kind == GDScriptParser::DataType::BUILTIN && parent_return_type.builtin_type == Variant::NIL)) { + valid = false; + } + } else { + valid = valid && is_type_compatible(parent_return_type, return_type); + } + } int par_count_diff = p_function->parameters.size() - parameters_types.size(); valid = valid && par_count_diff >= 0; valid = valid && default_value_count >= default_par_count + par_count_diff; - int i = 0; - for (const GDScriptParser::DataType &par_type : parameters_types) { - valid = valid && par_type == p_function->parameters[i++]->get_datatype(); + if (valid) { + int i = 0; + for (const GDScriptParser::DataType &parent_par_type : parameters_types) { + // Check parameter type contravariance. + GDScriptParser::DataType current_par_type = p_function->parameters[i++]->get_datatype(); + if (parent_par_type.is_variant() && parent_par_type.is_hard_type()) { + // `is_type_compatible()` returns `true` if one of the types is `Variant`. + // Don't allow narrowing a hard `Variant`. + valid = valid && current_par_type.is_variant(); + } else { + valid = valid && is_type_compatible(current_par_type, parent_par_type); + } + } } if (!valid) { @@ -1647,7 +1746,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * } parent_signature += ") -> "; - const String return_type = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant"; + const String return_type = parent_return_type.to_string_strict(); if (return_type == "null") { parent_signature += "void"; } else { @@ -1665,6 +1764,12 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * #endif // TOOLS_ENABLED } +#ifdef DEBUG_ENABLED + if (p_function->return_type == nullptr) { + parser->push_warning(p_function, GDScriptWarning::UNTYPED_DECLARATION, "Function", function_visible_name); + } +#endif + if (p_function->get_datatype().is_resolving()) { p_function->set_datatype(prev_datatype); } @@ -1746,7 +1851,7 @@ void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) { // Apply annotations. for (GDScriptParser::AnnotationNode *&E : stmt->annotations) { resolve_annotation(E); - E->apply(parser, stmt); + E->apply(parser, stmt, nullptr); } #ifdef DEBUG_ENABLED @@ -1757,6 +1862,7 @@ void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) { #endif // DEBUG_ENABLED resolve_node(stmt); + resolve_pending_lambda_bodies(); #ifdef DEBUG_ENABLED parser->ignored_warnings = previously_ignored_warnings; @@ -1772,6 +1878,15 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi bool is_constant = p_assignable->type == GDScriptParser::Node::CONSTANT; +#ifdef DEBUG_ENABLED + if (p_assignable->identifier != nullptr && p_assignable->identifier->suite != nullptr && p_assignable->identifier->suite->parent_block != nullptr) { + if (p_assignable->identifier->suite->parent_block->has_local(p_assignable->identifier->name)) { + const GDScriptParser::SuiteNode::Local &local = p_assignable->identifier->suite->parent_block->get_local(p_assignable->identifier->name); + parser->push_warning(p_assignable->identifier, GDScriptWarning::CONFUSABLE_LOCAL_DECLARATION, local.get_name(), p_assignable->identifier->name); + } + } +#endif + GDScriptParser::DataType specified_type; bool has_specified_type = p_assignable->datatype_specifier != nullptr; if (has_specified_type) { @@ -1784,8 +1899,8 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi if (p_assignable->initializer->type == GDScriptParser::Node::ARRAY) { GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(p_assignable->initializer); - if (has_specified_type && specified_type.has_container_element_type()) { - update_array_literal_element_type(array, specified_type.get_container_element_type()); + if (has_specified_type && specified_type.has_container_element_type(0)) { + update_array_literal_element_type(array, specified_type.get_container_element_type(0)); } } @@ -1848,7 +1963,7 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } else { push_error(vformat(R"(Cannot assign a value of type %s to %s "%s" with specified type %s.)", initializer_type.to_string(), p_kind, p_assignable->identifier->name, specified_type.to_string()), p_assignable->initializer); } - } else if (specified_type.has_container_element_type() && !initializer_type.has_container_element_type()) { + } else if (specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) { mark_node_unsafe(p_assignable->initializer); #ifdef DEBUG_ENABLED } else if (specified_type.builtin_type == Variant::INT && initializer_type.builtin_type == Variant::FLOAT) { @@ -1858,6 +1973,18 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } } +#ifdef DEBUG_ENABLED + if (!has_specified_type) { + const bool is_parameter = p_assignable->type == GDScriptParser::Node::PARAMETER; + const String declaration_type = is_constant ? "Constant" : (is_parameter ? "Parameter" : "Variable"); + if (p_assignable->infer_datatype || is_constant) { + parser->push_warning(p_assignable, GDScriptWarning::INFERRED_DECLARATION, declaration_type, p_assignable->identifier->name); + } else { + parser->push_warning(p_assignable, GDScriptWarning::UNTYPED_DECLARATION, declaration_type, p_assignable->identifier->name); + } + } +#endif + type.is_constant = is_constant; type.is_read_only = false; p_assignable->set_datatype(type); @@ -1874,9 +2001,8 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable } else if (p_variable->assignments == 0) { parser->push_warning(p_variable, GDScriptWarning::UNASSIGNED_VARIABLE, p_variable->identifier->name); } - - is_shadowing(p_variable->identifier, kind); } + is_shadowing(p_variable->identifier, kind, p_is_local); #endif } @@ -1889,9 +2015,8 @@ void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant if (p_constant->usages == 0) { parser->push_warning(p_constant, GDScriptWarning::UNUSED_LOCAL_CONSTANT, p_constant->identifier->name); } - - is_shadowing(p_constant->identifier, kind); } + is_shadowing(p_constant->identifier, kind, p_is_local); #endif } @@ -1994,21 +2119,24 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { } GDScriptParser::DataType variable_type; + String list_visible_type = "<unresolved type>"; if (list_resolved) { variable_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; variable_type.kind = GDScriptParser::DataType::BUILTIN; variable_type.builtin_type = Variant::INT; + list_visible_type = "Array[int]"; // NOTE: `range()` has `Array` return type. } else if (p_for->list) { resolve_node(p_for->list, false); GDScriptParser::DataType list_type = p_for->list->get_datatype(); + list_visible_type = list_type.to_string(); if (!list_type.is_hard_type()) { mark_node_unsafe(p_for->list); } if (list_type.is_variant()) { variable_type.kind = GDScriptParser::DataType::VARIANT; mark_node_unsafe(p_for->list); - } else if (list_type.has_container_element_type()) { - variable_type = list_type.get_container_element_type(); + } else if (list_type.has_container_element_type(0)) { + variable_type = list_type.get_container_element_type(0); variable_type.type_source = list_type.type_source; } else if (list_type.is_typed_container_type()) { variable_type = list_type.get_typed_container_type(); @@ -2044,15 +2172,46 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { push_error(vformat(R"(Unable to iterate on value of type "%s".)", list_type.to_string()), p_for->list); } } + if (p_for->variable) { - p_for->variable->set_datatype(variable_type); + if (p_for->datatype_specifier) { + GDScriptParser::DataType specified_type = type_from_metatype(resolve_datatype(p_for->datatype_specifier)); + if (!specified_type.is_variant()) { + if (variable_type.is_variant() || !variable_type.is_hard_type()) { + mark_node_unsafe(p_for->variable); + p_for->use_conversion_assign = true; + } else if (!is_type_compatible(specified_type, variable_type, true, p_for->variable)) { + if (is_type_compatible(variable_type, specified_type)) { + mark_node_unsafe(p_for->variable); + p_for->use_conversion_assign = true; + } else { + push_error(vformat(R"(Unable to iterate on value of type "%s" with variable of type "%s".)", list_visible_type, specified_type.to_string()), p_for->datatype_specifier); + } + } else if (!is_type_compatible(specified_type, variable_type)) { + p_for->use_conversion_assign = true; + } + if (p_for->list && p_for->list->type == GDScriptParser::Node::ARRAY) { + update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type); + } + } + p_for->variable->set_datatype(specified_type); + } else { + p_for->variable->set_datatype(variable_type); +#ifdef DEBUG_ENABLED + if (variable_type.is_hard_type()) { + parser->push_warning(p_for->variable, GDScriptWarning::INFERRED_DECLARATION, R"("for" iterator variable)", p_for->variable->name); + } else { + parser->push_warning(p_for->variable, GDScriptWarning::UNTYPED_DECLARATION, R"("for" iterator variable)", p_for->variable->name); + } +#endif + } } resolve_suite(p_for->loop); p_for->set_datatype(p_for->loop->get_datatype()); #ifdef DEBUG_ENABLED if (p_for->variable) { - is_shadowing(p_for->variable, R"("for" iterator variable)"); + is_shadowing(p_for->variable, R"("for" iterator variable)", true); } #endif } @@ -2101,6 +2260,10 @@ void GDScriptAnalyzer::resolve_match_branch(GDScriptParser::MatchBranchNode *p_m resolve_match_pattern(p_match_branch->patterns[i], p_match_test); } + if (p_match_branch->guard_body) { + resolve_suite(p_match_branch->guard_body); + } + resolve_suite(p_match_branch->block); decide_suite_type(p_match_branch, p_match_branch->block); @@ -2148,7 +2311,7 @@ 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"); + is_shadowing(p_match_pattern->bind, "pattern bind", true); 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); } @@ -2222,8 +2385,8 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) { result.builtin_type = Variant::NIL; result.is_constant = true; } else { - if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type()) { - update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type()); + if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type(0)) { + update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type(0)); } if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) { update_const_expression_builtin_type(p_return->return_value, expected_type, "return"); @@ -2351,6 +2514,14 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre case GDScriptParser::Node::WHILE: ERR_FAIL_MSG("Reaching unreachable case"); } + + if (p_expression->get_datatype().kind == GDScriptParser::DataType::UNRESOLVED) { + // Prevent `is_type_compatible()` errors for incomplete expressions. + // The error can still occur if `reduce_*()` is called directly. + GDScriptParser::DataType dummy; + dummy.kind = GDScriptParser::DataType::VARIANT; + p_expression->set_datatype(dummy); + } } void GDScriptAnalyzer::reduce_array(GDScriptParser::ArrayNode *p_array) { @@ -2434,28 +2605,31 @@ void GDScriptAnalyzer::update_const_expression_builtin_type(GDScriptParser::Expr // When an array literal is stored (or passed as function argument) to a typed context, we then assume the array is typed. // This function determines which type is that (if any). void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type) { + GDScriptParser::DataType expected_type = p_element_type; + expected_type.container_element_types.clear(); // Nested types (like `Array[Array[int]]`) are not currently supported. + for (int i = 0; i < p_array->elements.size(); i++) { GDScriptParser::ExpressionNode *element_node = p_array->elements[i]; if (element_node->is_constant) { - update_const_expression_builtin_type(element_node, p_element_type, "include"); + update_const_expression_builtin_type(element_node, expected_type, "include"); } - const GDScriptParser::DataType &element_type = element_node->get_datatype(); - if (element_type.has_no_type() || element_type.is_variant() || !element_type.is_hard_type()) { + const GDScriptParser::DataType &actual_type = element_node->get_datatype(); + if (actual_type.has_no_type() || actual_type.is_variant() || !actual_type.is_hard_type()) { mark_node_unsafe(element_node); continue; } - if (!is_type_compatible(p_element_type, element_type, true, p_array)) { - if (is_type_compatible(element_type, p_element_type)) { + if (!is_type_compatible(expected_type, actual_type, true, p_array)) { + if (is_type_compatible(actual_type, expected_type)) { mark_node_unsafe(element_node); continue; } - push_error(vformat(R"(Cannot have an element of type "%s" in an array of type "Array[%s]".)", element_type.to_string(), p_element_type.to_string()), element_node); + push_error(vformat(R"(Cannot have an element of type "%s" in an array of type "Array[%s]".)", actual_type.to_string(), expected_type.to_string()), element_node); return; } } GDScriptParser::DataType array_type = p_array->get_datatype(); - array_type.set_container_element_type(p_element_type); + array_type.set_container_element_type(0, expected_type); p_array->set_datatype(array_type); } @@ -2469,9 +2643,15 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype(); - if (assignee_type.is_constant || (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->is_constant)) { + if (assignee_type.is_constant) { push_error("Cannot assign a new value to a constant.", p_assignment->assignee); return; + } else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->is_constant) { + const GDScriptParser::DataType &base_type = static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->datatype; + if (base_type.kind != GDScriptParser::DataType::SCRIPT && base_type.kind != GDScriptParser::DataType::CLASS) { // Static variables. + push_error("Cannot assign a new value to a constant.", p_assignment->assignee); + return; + } } else if (assignee_type.is_read_only) { push_error("Cannot assign a new value to a read-only property.", p_assignment->assignee); return; @@ -2496,8 +2676,8 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig } // Check if assigned value is an array literal, so we can make it a typed array too if appropriate. - if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.has_container_element_type()) { - update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type()); + if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.is_hard_type() && assignee_type.has_container_element_type(0)) { + update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type(0)); } if (p_assignment->operation == GDScriptParser::AssignmentNode::OP_NONE && assignee_type.is_hard_type() && p_assignment->assigned_value->is_constant) { @@ -2575,7 +2755,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig // weak non-variant assignee and incompatible result downgrades_assignee = true; } - } else if (assignee_type.has_container_element_type() && !op_type.has_container_element_type()) { + } else if (assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) { // typed array assignee and untyped array result mark_node_unsafe(p_assignment); } @@ -2775,19 +2955,20 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a if (!p_call->is_super && callee_type == GDScriptParser::Node::IDENTIFIER) { // Call to name directly. StringName function_name = p_call->function_name; - Variant::Type builtin_type = GDScriptParser::get_builtin_type(function_name); + if (function_name == SNAME("Object")) { + push_error(R"*(Invalid constructor "Object()", use "Object.new()" instead.)*", p_call); + p_call->set_datatype(call_type); + return; + } + + Variant::Type builtin_type = GDScriptParser::get_builtin_type(function_name); if (builtin_type < Variant::VARIANT_MAX) { // Is a builtin constructor. call_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; call_type.kind = GDScriptParser::DataType::BUILTIN; call_type.builtin_type = builtin_type; - if (builtin_type == Variant::OBJECT) { - call_type.kind = GDScriptParser::DataType::NATIVE; - call_type.native_type = function_name; // "Object". - } - bool safe_to_fold = true; switch (builtin_type) { // Those are stored by reference so not suited for compile-time construction. @@ -2823,7 +3004,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a switch (err.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: - push_error(vformat(R"(Invalid argument for %s constructor: argument %d should be "%s" but is "%s".)", Variant::get_type_name(builtin_type), err.argument + 1, + push_error(vformat(R"*(Invalid argument for "%s()" constructor: argument %d should be "%s" but is "%s".)*", Variant::get_type_name(builtin_type), err.argument + 1, Variant::get_type_name(Variant::Type(err.expected)), p_call->arguments[err.argument]->get_datatype().to_string()), p_call->arguments[err.argument]); break; @@ -2839,10 +3020,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call->callee); } break; case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: - push_error(vformat(R"(Too many arguments for %s constructor. Received %d but expected %d.)", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); + push_error(vformat(R"*(Too many 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_TOO_FEW_ARGUMENTS: - 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); + 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: @@ -2853,21 +3034,41 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a break; } } else { - // TODO: Check constructors without constants. - // If there's one argument, try to use copy constructor (those aren't explicitly defined). if (p_call->arguments.size() == 1) { GDScriptParser::DataType arg_type = p_call->arguments[0]->get_datatype(); - if (arg_type.is_variant()) { - mark_node_unsafe(p_call->arguments[0]); - } else { + if (arg_type.is_hard_type() && !arg_type.is_variant()) { if (arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == builtin_type) { // Okay. p_call->set_datatype(call_type); return; } + } else { +#ifdef DEBUG_ENABLED + mark_node_unsafe(p_call); + // Constructors support overloads. + Vector<String> types; + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + if (i != builtin_type && Variant::can_convert_strict((Variant::Type)i, builtin_type)) { + types.push_back(Variant::get_type_name((Variant::Type)i)); + } + } + String expected_types = function_name; + if (types.size() == 1) { + expected_types += "\" or \"" + types[0]; + } else if (types.size() >= 2) { + for (int i = 0; i < types.size() - 1; i++) { + expected_types += "\", \"" + types[i]; + } + expected_types += "\", or \"" + types[types.size() - 1]; + } + parser->push_warning(p_call->arguments[0], GDScriptWarning::UNSAFE_CALL_ARGUMENT, "1", "constructor", function_name, expected_types, "Variant"); +#endif + p_call->set_datatype(call_type); + return; } } + List<MethodInfo> constructors; Variant::get_constructor_list(builtin_type, &constructors); bool match = false; @@ -2884,14 +3085,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a for (int i = 0; i < p_call->arguments.size(); i++) { GDScriptParser::DataType par_type = type_from_property(info.arguments[i], true); - - if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype(), true)) { + GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype(); + if (!is_type_compatible(par_type, arg_type, true)) { types_match = false; break; #ifdef DEBUG_ENABLED } else { - if (par_type.builtin_type == Variant::INT && p_call->arguments[i]->get_datatype().builtin_type == Variant::FLOAT && builtin_type != Variant::INT) { - parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); + if (par_type.builtin_type == Variant::INT && arg_type.builtin_type == Variant::FLOAT && builtin_type != Variant::INT) { + parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, function_name); } #endif } @@ -2899,9 +3100,19 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a if (types_match) { for (int i = 0; i < p_call->arguments.size(); i++) { + GDScriptParser::DataType par_type = type_from_property(info.arguments[i], true); if (p_call->arguments[i]->is_constant) { - update_const_expression_builtin_type(p_call->arguments[i], type_from_property(info.arguments[i], true), "pass"); + update_const_expression_builtin_type(p_call->arguments[i], par_type, "pass"); + } +#ifdef DEBUG_ENABLED + if (!(par_type.is_variant() && par_type.is_hard_type())) { + GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype(); + if (arg_type.is_variant() || !arg_type.is_hard_type()) { + mark_node_unsafe(p_call); + parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), "constructor", function_name, par_type.to_string(), arg_type.to_string_strict()); + } } +#endif } match = true; call_type = type_from_property(info.return_val); @@ -2942,12 +3153,16 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a GDScriptUtilityFunctions::get_function(function_name)(&value, (const Variant **)args.ptr(), args.size(), err); switch (err.error) { - case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { - PropertyInfo wrong_arg = function_info.arguments[err.argument]; - 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, true).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()), - p_call->arguments[err.argument]); - } break; + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: + if (value.get_type() == Variant::STRING && !value.operator String().is_empty()) { + push_error(vformat(R"*(Invalid argument for "%s()" function: %s)*", function_name, value), p_call->arguments[err.argument]); + } else { + // Do not use `type_from_property()` for expected type, since utility functions use their own checks. + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1, + Variant::get_type_name((Variant::Type)err.expected), p_call->arguments[err.argument]->get_datatype().to_string()), + p_call->arguments[err.argument]); + } + break; case Callable::CallError::CALL_ERROR_INVALID_METHOD: push_error(vformat(R"(Invalid call for function "%s".)", function_name), p_call); break; @@ -2989,18 +3204,16 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a Variant::call_utility_function(function_name, &value, (const Variant **)args.ptr(), args.size(), err); switch (err.error) { - case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { - String expected_type_name; - if (err.argument < function_info.arguments.size()) { - expected_type_name = type_from_property(function_info.arguments[err.argument], true).to_string(); + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: + if (value.get_type() == Variant::STRING && !value.operator String().is_empty()) { + push_error(vformat(R"*(Invalid argument for "%s()" function: %s)*", function_name, value), p_call->arguments[err.argument]); } else { - expected_type_name = Variant::get_type_name((Variant::Type)err.expected); + // Do not use `type_from_property()` for expected type, since utility functions use their own checks. + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1, + Variant::get_type_name((Variant::Type)err.expected), p_call->arguments[err.argument]->get_datatype().to_string()), + p_call->arguments[err.argument]); } - - push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1, - expected_type_name, p_call->arguments[err.argument]->get_datatype().to_string()), - p_call->arguments[err.argument]); - } break; + break; case Callable::CallError::CALL_ERROR_INVALID_METHOD: push_error(vformat(R"(Invalid call for function "%s".)", function_name), p_call); break; @@ -3035,7 +3248,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a base_type.is_meta_type = false; is_self = true; - if (p_call->callee == nullptr && !lambda_stack.is_empty()) { + if (p_call->callee == nullptr && current_lambda != nullptr) { push_error("Cannot use `super()` inside a lambda.", p_call); } } else if (callee_type == GDScriptParser::Node::IDENTIFIER) { @@ -3090,22 +3303,24 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a bool is_constructor = (base_type.is_meta_type || (p_call->callee && p_call->callee->type == GDScriptParser::Node::IDENTIFIER)) && p_call->function_name == SNAME("new"); + if (is_constructor && Engine::get_singleton()->has_singleton(base_type.native_type)) { + push_error(vformat(R"(Cannot construct native class "%s" because it is an engine singleton.)", base_type.native_type), p_call); + p_call->set_datatype(call_type); + return; + } + if (get_function_signature(p_call, is_constructor, base_type, p_call->function_name, return_type, par_types, default_arg_count, method_flags)) { // If the method is implemented in the class hierarchy, the virtual flag will not be set for that MethodInfo and the search stops there. - // MethodInfo's above the class that defines the method might still have the virtual flag set. - if (method_flags.has_flag(METHOD_FLAG_VIRTUAL)) { - if (p_call->is_super) { - push_error(vformat(R"*(Cannot call the parent class' virtual function "%s()" because it hasn't been defined.)*", p_call->function_name), p_call); - } else { - push_error(vformat(R"*(Cannot call virtual function "%s()" because it hasn't been defined.)*", p_call->function_name), p_call); - } + // Virtual check only possible for super() calls because class hierarchy is known. Node/Objects may have scripts attached we don't know of at compile-time. + if (p_call->is_super && method_flags.has_flag(METHOD_FLAG_VIRTUAL)) { + push_error(vformat(R"*(Cannot call the parent class' virtual function "%s()" because it hasn't been defined.)*", p_call->function_name), p_call); } - // If the function require typed arrays we must make literals be typed. + // If the function requires typed arrays we must make literals be typed. for (const KeyValue<int, GDScriptParser::ArrayNode *> &E : arrays) { int index = E.key; - if (index < par_types.size() && par_types[index].has_container_element_type()) { - update_array_literal_element_type(E.value, par_types[index].get_container_element_type()); + if (index < par_types.size() && par_types[index].is_hard_type() && par_types[index].has_container_element_type(0)) { + update_array_literal_element_type(E.value, par_types[index].get_container_element_type(0)); } } validate_call_arg(par_types, default_arg_count, method_flags.has_flag(METHOD_FLAG_VARARG), p_call); @@ -3116,15 +3331,16 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } if (is_self && static_context && !method_flags.has_flag(METHOD_FLAG_STATIC)) { - if (parser->current_function) { - // Get the parent function above any lambda. - GDScriptParser::FunctionNode *parent_function = parser->current_function; - while (parent_function->source_lambda) { - parent_function = parent_function->source_lambda->parent_function; - } + // Get the parent function above any lambda. + GDScriptParser::FunctionNode *parent_function = parser->current_function; + while (parent_function && parent_function->source_lambda) { + parent_function = parent_function->source_lambda->parent_function; + } + + if (parent_function) { push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parent_function->identifier->name), p_call); } else { - push_error(vformat(R"*(Cannot call non-static function "%s()" for static variable initializer.)*", p_call->function_name), p_call); + push_error(vformat(R"*(Cannot call non-static function "%s()" from a static variable initializer.)*", p_call->function_name), p_call); } } else if (!is_self && base_type.is_meta_type && !method_flags.has_flag(METHOD_FLAG_STATIC)) { base_type.is_meta_type = false; // For `to_string()`. @@ -3205,8 +3421,8 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a #else push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee); #endif // SUGGEST_GODOT4_RENAMES - } else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::NATIVE && base_type.is_meta_type)) { - push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type), p_call); + } else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.is_meta_type)) { + push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.to_string()), p_call); } } @@ -3236,8 +3452,8 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { } } - if (p_cast->operand->type == GDScriptParser::Node::ARRAY && cast_type.has_container_element_type()) { - update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_cast->operand), cast_type.get_container_element_type()); + if (p_cast->operand->type == GDScriptParser::Node::ARRAY && cast_type.has_container_element_type(0)) { + update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_cast->operand), cast_type.get_container_element_type(0)); } if (!cast_type.is_variant()) { @@ -3298,17 +3514,26 @@ void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dicti void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) { GDScriptParser::DataType result; - result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - result.kind = GDScriptParser::DataType::NATIVE; - result.native_type = SNAME("Node"); - result.builtin_type = Variant::OBJECT; + result.kind = GDScriptParser::DataType::VARIANT; - if (!ClassDB::is_parent_class(parser->current_class->base_type.native_type, result.native_type)) { - push_error(R"*(Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node.)*", p_get_node); + if (!ClassDB::is_parent_class(parser->current_class->base_type.native_type, SNAME("Node"))) { + push_error(vformat(R"*(Cannot use shorthand "get_node()" notation ("%c") on a class that isn't a node.)*", p_get_node->use_dollar ? '$' : '%'), p_get_node); + p_get_node->set_datatype(result); + return; + } + + if (static_context) { + push_error(vformat(R"*(Cannot use shorthand "get_node()" notation ("%c") in a static function.)*", p_get_node->use_dollar ? '$' : '%'), p_get_node); + p_get_node->set_datatype(result); + return; } mark_lambda_use_self(); + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + result.kind = GDScriptParser::DataType::NATIVE; + result.builtin_type = Variant::OBJECT; + result.native_type = SNAME("Node"); p_get_node->set_datatype(result); } @@ -3345,7 +3570,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base_set_class(GDScriptParser::Ide p_identifier->set_datatype(p_identifier_datatype); Error err = OK; - Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_identifier_datatype.script_path, err); + Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_identifier_datatype.script_path, err, parser->script_path); if (err) { push_error(vformat(R"(Error while getting cache for script "%s".)", p_identifier_datatype.script_path), p_identifier); return; @@ -3366,7 +3591,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod base = *p_base; } - const StringName &name = p_identifier->name; + StringName name = p_identifier->name; if (base.kind == GDScriptParser::DataType::ENUM) { if (base.is_meta_type) { @@ -3411,7 +3636,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; } @@ -3433,6 +3658,10 @@ 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(); @@ -3461,12 +3690,21 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod get_class_node_current_scope_classes(base_class, &script_classes); } + bool is_constructor = base.is_meta_type && p_identifier->name == SNAME("new"); + for (GDScriptParser::ClassNode *script_class : script_classes) { if (p_base == nullptr && script_class->identifier && script_class->identifier->name == name) { reduce_identifier_from_base_set_class(p_identifier, script_class->get_datatype()); + if (script_class->outer != nullptr) { + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CLASS; + } return; } + if (is_constructor) { + name = "_init"; + } + if (script_class->has_member(name)) { resolve_class_member(script_class, name, p_identifier); @@ -3516,14 +3754,16 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } break; case GDScriptParser::ClassNode::Member::FUNCTION: { - if (is_base && !base.is_meta_type) { + if (is_base && (!base.is_meta_type || member.function->is_static)) { p_identifier->set_datatype(make_callable_type(member.function->info)); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_FUNCTION; return; } } break; case GDScriptParser::ClassNode::Member::CLASS: { reduce_identifier_from_base_set_class(p_identifier, member.get_datatype()); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CLASS; return; } @@ -3541,10 +3781,68 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } } + // Check non-GDScript scripts. + Ref<Script> script_type = base.script_type; + + if (base_class == nullptr && script_type.is_valid()) { + List<PropertyInfo> property_list; + script_type->get_script_property_list(&property_list); + + for (const PropertyInfo &property_info : property_list) { + if (property_info.name != p_identifier->name) { + continue; + } + + const GDScriptParser::DataType property_type = GDScriptAnalyzer::type_from_property(property_info, false, false); + + p_identifier->set_datatype(property_type); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE; + return; + } + + MethodInfo method_info = script_type->get_method_info(p_identifier->name); + + if (method_info.name == p_identifier->name) { + p_identifier->set_datatype(make_callable_type(method_info)); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_FUNCTION; + return; + } + + List<MethodInfo> signal_list; + script_type->get_script_signal_list(&signal_list); + + for (const MethodInfo &signal_info : signal_list) { + if (signal_info.name != p_identifier->name) { + continue; + } + + const GDScriptParser::DataType signal_type = make_signal_type(signal_info); + + p_identifier->set_datatype(signal_type); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_SIGNAL; + return; + } + + HashMap<StringName, Variant> constant_map; + script_type->get_constants(&constant_map); + + if (constant_map.has(p_identifier->name)) { + Variant constant = constant_map.get(p_identifier->name); + + p_identifier->set_datatype(make_builtin_meta_type(constant.get_type())); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; + return; + } + } + // 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)) { + if (is_constructor) { + name = "_init"; + } + MethodInfo method_info; if (ClassDB::has_property(native, name)) { StringName getter_name = ClassDB::get_property_getter(native, name); @@ -3658,10 +3956,19 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident found_source = true; } break; case GDScriptParser::IdentifierNode::UNDEFINED_SOURCE: + case GDScriptParser::IdentifierNode::MEMBER_FUNCTION: + case GDScriptParser::IdentifierNode::MEMBER_CLASS: break; } +#ifdef DEBUG_ENABLED + if (!found_source && p_identifier->suite != nullptr && p_identifier->suite->has_local(p_identifier->name)) { + parser->push_warning(p_identifier, GDScriptWarning::CONFUSABLE_LOCAL_USAGE, p_identifier->name); + } +#endif + // Not a local, so check members. + if (!found_source) { reduce_identifier_from_base(p_identifier); if (p_identifier->source != GDScriptParser::IdentifierNode::UNDEFINED_SOURCE || p_identifier->get_datatype().is_set()) { @@ -3674,19 +3981,20 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident bool source_is_variable = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE; bool source_is_signal = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_SIGNAL; if ((source_is_variable || source_is_signal) && static_context) { - if (parser->current_function) { - // Get the parent function above any lambda. - GDScriptParser::FunctionNode *parent_function = parser->current_function; - while (parent_function->source_lambda) { - parent_function = parent_function->source_lambda->parent_function; - } + // Get the parent function above any lambda. + GDScriptParser::FunctionNode *parent_function = parser->current_function; + while (parent_function && parent_function->source_lambda) { + parent_function = parent_function->source_lambda->parent_function; + } + + if (parent_function) { push_error(vformat(R"*(Cannot access %s "%s" from the static function "%s()".)*", source_is_signal ? "signal" : "instance variable", p_identifier->name, parent_function->identifier->name), p_identifier); } else { - push_error(vformat(R"*(Cannot access %s "%s" for a static variable initializer.)*", source_is_signal ? "signal" : "instance variable", p_identifier->name), p_identifier); + push_error(vformat(R"*(Cannot access %s "%s" from a static variable initializer.)*", source_is_signal ? "signal" : "instance variable", p_identifier->name), p_identifier); } } - if (!lambda_stack.is_empty()) { + if (current_lambda != nullptr) { // If the identifier is a member variable (including the native class properties) or a signal, we consider the lambda to be using `self`, so we keep a reference to the current instance. if (source_is_variable || source_is_signal) { mark_lambda_use_self(); @@ -3698,7 +4006,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident return; } - GDScriptParser::FunctionNode *function_test = lambda_stack.back()->get()->function; + GDScriptParser::FunctionNode *function_test = current_lambda->function; // Make sure we aren't capturing variable in the same lambda. // This also add captures for nested lambdas. while (function_test != nullptr && function_test != p_identifier->source_function && function_test->source_lambda != nullptr && !function_test->source_lambda->captures_indices.has(p_identifier->name)) { @@ -3714,10 +4022,10 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident StringName name = p_identifier->name; p_identifier->source = GDScriptParser::IdentifierNode::UNDEFINED_SOURCE; - // Check globals. We make an exception for Variant::OBJECT because it's the base class for - // non-builtin types so we allow doing e.g. Object.new() + // Not a local or a member, so check globals. + Variant::Type builtin_type = GDScriptParser::get_builtin_type(name); - if (builtin_type != Variant::OBJECT && builtin_type < Variant::VARIANT_MAX) { + if (builtin_type < Variant::VARIANT_MAX) { if (can_be_builtin) { p_identifier->set_datatype(make_builtin_meta_type(builtin_type)); return; @@ -3743,8 +4051,10 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident if (autoload.is_singleton) { // Singleton exists, so it's at least a Node. GDScriptParser::DataType result; - result.kind = GDScriptParser::DataType::NATIVE; result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + result.kind = GDScriptParser::DataType::NATIVE; + result.builtin_type = Variant::OBJECT; + result.native_type = SNAME("Node"); if (ResourceLoader::get_resource_type(autoload.path) == "GDScript") { Ref<GDScriptParserRef> singl_parser = get_parser_for(autoload.path); if (singl_parser.is_valid()) { @@ -3853,34 +4163,12 @@ void GDScriptAnalyzer::reduce_lambda(GDScriptParser::LambdaNode *p_lambda) { return; } - lambda_stack.push_back(p_lambda); + GDScriptParser::LambdaNode *previous_lambda = current_lambda; + current_lambda = p_lambda; resolve_function_signature(p_lambda->function, p_lambda, true); - resolve_function_body(p_lambda->function, true); - lambda_stack.pop_back(); + current_lambda = previous_lambda; - int captures_amount = p_lambda->captures.size(); - if (captures_amount > 0) { - // Create space for lambda parameters. - // At the beginning to not mess with optional parameters. - int param_count = p_lambda->function->parameters.size(); - p_lambda->function->parameters.resize(param_count + captures_amount); - for (int i = param_count - 1; i >= 0; i--) { - p_lambda->function->parameters.write[i + captures_amount] = p_lambda->function->parameters[i]; - p_lambda->function->parameters_indices[p_lambda->function->parameters[i]->identifier->name] = i + captures_amount; - } - - // Add captures as extra parameters at the beginning. - for (int i = 0; i < p_lambda->captures.size(); i++) { - GDScriptParser::IdentifierNode *capture = p_lambda->captures[i]; - GDScriptParser::ParameterNode *capture_param = parser->alloc_node<GDScriptParser::ParameterNode>(); - capture_param->identifier = capture; - capture_param->usages = capture->usages; - capture_param->set_datatype(capture->get_datatype()); - - p_lambda->function->parameters.write[i] = capture_param; - p_lambda->function->parameters_indices[capture->name] = i; - } - } + pending_body_resolution_lambdas.push_back(p_lambda); } void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) { @@ -3980,7 +4268,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); bool valid = false; // If the base is a metatype, use the analyzer instead. - if (p_subscript->base->is_constant && !base_type.is_meta_type) { + if (p_subscript->base->is_constant && !base_type.is_meta_type && base_type.kind != GDScriptParser::DataType::CLASS) { // Just try to get it. Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid); if (valid) { @@ -4129,7 +4417,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri } } else if (base_type.kind != GDScriptParser::DataType::BUILTIN && !index_type.is_variant()) { if (index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME) { - push_error(vformat(R"(Only String or StringName can be used as index for type "%s", but received a "%s".)", base_type.to_string(), index_type.to_string()), p_subscript->index); + push_error(vformat(R"(Only "String" or "StringName" can be used as index for type "%s", but received "%s".)", base_type.to_string(), index_type.to_string()), p_subscript->index); } } @@ -4210,8 +4498,8 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri break; // Can have an element type. case Variant::ARRAY: - if (base_type.has_container_element_type()) { - result_type = base_type.get_container_element_type(); + if (base_type.has_container_element_type(0)) { + result_type = base_type.get_container_element_type(0); result_type.type_source = base_type.type_source; } else { result_type.kind = GDScriptParser::DataType::VARIANT; @@ -4375,7 +4663,7 @@ Variant GDScriptAnalyzer::make_expression_reduced_value(GDScriptParser::Expressi } Variant GDScriptAnalyzer::make_array_reduced_value(GDScriptParser::ArrayNode *p_array, bool &is_reduced) { - Array array = p_array->get_datatype().has_container_element_type() ? make_array_from_element_datatype(p_array->get_datatype().get_container_element_type()) : Array(); + Array array = p_array->get_datatype().has_container_element_type(0) ? make_array_from_element_datatype(p_array->get_datatype().get_container_element_type(0)) : Array(); array.resize(p_array->elements.size()); for (int i = 0; i < p_array->elements.size(); i++) { @@ -4468,7 +4756,7 @@ Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::D Ref<Script> script_type = p_element_datatype.script_type; if (p_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) { Error err = OK; - Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_element_datatype.script_path, err); + Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_element_datatype.script_path, err, parser->script_path); if (err) { push_error(vformat(R"(Error while getting cache for script "%s".)", p_element_datatype.script_path), p_source_node); return array; @@ -4497,8 +4785,8 @@ Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNo GDScriptParser::DataType datatype = p_variable->get_datatype(); if (datatype.is_hard_type()) { if (datatype.kind == GDScriptParser::DataType::BUILTIN && datatype.builtin_type != Variant::OBJECT) { - if (datatype.builtin_type == Variant::ARRAY && datatype.has_container_element_type()) { - result = make_array_from_element_datatype(datatype.get_container_element_type()); + if (datatype.builtin_type == Variant::ARRAY && datatype.has_container_element_type(0)) { + result = make_array_from_element_datatype(datatype.get_container_element_type(0)); } else { VariantInternal::initialize(&result, datatype.builtin_type); } @@ -4511,6 +4799,10 @@ Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNo return result; } +const HashMap<String, Ref<GDScriptParserRef>> &GDScriptAnalyzer::get_depended_parsers() { + return depended_parsers; +} + GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source) { GDScriptParser::DataType result; result.is_constant = true; @@ -4518,7 +4810,16 @@ 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::ARRAY) { + const Array &array = p_value; + if (array.get_typed_script()) { + result.set_container_element_type(0, type_from_metatype(make_script_meta_type(array.get_typed_script()))); + } else if (array.get_typed_class_name()) { + result.set_container_element_type(0, type_from_metatype(make_native_meta_type(array.get_typed_class_name()))); + } else if (array.get_typed_builtin() != Variant::NIL) { + result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)array.get_typed_builtin()))); + } + } else if (p_value.get_type() == Variant::OBJECT) { // Object is treated as a native type, not a builtin type. result.kind = GDScriptParser::DataType::NATIVE; @@ -4607,8 +4908,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; @@ -4626,7 +4938,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo } else if (class_exists(elem_type_name)) { elem_type.kind = GDScriptParser::DataType::NATIVE; elem_type.builtin_type = Variant::OBJECT; - elem_type.native_type = p_property.hint_string; + elem_type.native_type = elem_type_name; } else if (ScriptServer::is_global_class(elem_type_name)) { // Just load this as it shouldn't be a GDScript. Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(elem_type_name)); @@ -4638,7 +4950,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed array."); } elem_type.is_constant = false; - result.set_container_element_type(elem_type); + result.set_container_element_type(0, elem_type); } else if (p_property.type == Variant::INT) { // Check if it's enum. if ((p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && p_property.class_name != StringName()) { @@ -4854,29 +5166,36 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype(); if (arg_type.is_variant() || !arg_type.is_hard_type()) { +#ifdef DEBUG_ENABLED // Argument can be anything, so this is unsafe (unless the parameter is a hard variant). if (!(par_type.is_hard_type() && par_type.is_variant())) { mark_node_unsafe(p_call->arguments[i]); + parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), "function", p_call->function_name, par_type.to_string(), arg_type.to_string_strict()); } +#endif } else if (par_type.is_hard_type() && !is_type_compatible(par_type, arg_type, true)) { - // Supertypes are acceptable for dynamic compliance, but it's unsafe. - mark_node_unsafe(p_call); if (!is_type_compatible(arg_type, par_type)) { push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", p_call->function_name, i + 1, par_type.to_string(), arg_type.to_string()), p_call->arguments[i]); +#ifdef DEBUG_ENABLED + } else { + // Supertypes are acceptable for dynamic compliance, but it's unsafe. + mark_node_unsafe(p_call); + parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), "function", p_call->function_name, par_type.to_string(), arg_type.to_string_strict()); +#endif } #ifdef DEBUG_ENABLED } else if (par_type.kind == GDScriptParser::DataType::BUILTIN && par_type.builtin_type == Variant::INT && arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == Variant::FLOAT) { - parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); + parser->push_warning(p_call->arguments[i], GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); #endif } } } #ifdef DEBUG_ENABLED -void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context) { - const StringName &name = p_local->name; +void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope) { + const StringName &name = p_identifier->name; GDScriptParser::DataType base = parser->current_class->get_datatype(); GDScriptParser::ClassNode *base_class = base.class_type; @@ -4886,29 +5205,34 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con for (MethodInfo &info : gdscript_funcs) { if (info.name == name) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in function"); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in function"); return; } } - if (Variant::has_utility_function(name)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in function"); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in function"); return; } else if (ClassDB::class_exists(name)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "global class"); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "native class"); + return; + } else if (ScriptServer::is_global_class(name)) { + String class_path = ScriptServer::get_global_class_path(name).get_file(); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, vformat(R"(global class defined in "%s")", class_path)); return; - } else if (GDScriptParser::get_builtin_type(name) != Variant::VARIANT_MAX) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in type"); + } else if (GDScriptParser::get_builtin_type(name) < Variant::VARIANT_MAX) { + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in type"); return; } } - while (base_class != nullptr) { - if (base_class->has_member(name)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE, p_context, p_local->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line())); - return; + if (p_in_local_scope) { + while (base_class != nullptr) { + if (base_class->has_member(name)) { + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE, p_context, p_identifier->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line())); + return; + } + base_class = base_class->base_type.class_type; } - base_class = base_class->base_type.class_type; } StringName parent = base.native_type; @@ -4916,19 +5240,19 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con ERR_FAIL_COND_MSG(!class_exists(parent), "Non-existent native base class."); if (ClassDB::has_method(parent, name, true)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "method", parent); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "method", parent); return; } else if (ClassDB::has_signal(parent, name, true)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "signal", parent); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "signal", parent); return; } else if (ClassDB::has_property(parent, name, true)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "property", parent); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "property", parent); return; } else if (ClassDB::has_integer_constant(parent, name, true)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "constant", parent); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "constant", parent); return; } else if (ClassDB::has_enum(parent, name, true)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "enum", parent); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "enum", parent); return; } parent = ClassDB::get_parent_class(parent); @@ -4945,6 +5269,17 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator } GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source) { + if (p_operation == Variant::OP_AND || p_operation == Variant::OP_OR) { + // Those work for any type of argument and always return a boolean. + // They don't use the Variant operator since they have short-circuit semantics. + r_valid = true; + GDScriptParser::DataType result; + result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + result.kind = GDScriptParser::DataType::BUILTIN; + result.builtin_type = Variant::BOOL; + return result; + } + Variant::Type a_type = p_a.builtin_type; Variant::Type b_type = p_b.builtin_type; @@ -4963,11 +5298,21 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator } } - Variant::ValidatedOperatorEvaluator op_eval = Variant::get_validated_operator_evaluator(p_operation, a_type, b_type); + GDScriptParser::DataType result; bool hard_operation = p_a.is_hard_type() && p_b.is_hard_type(); + + if (p_operation == Variant::OP_ADD && a_type == Variant::ARRAY && b_type == Variant::ARRAY) { + if (p_a.has_container_element_type(0) && p_b.has_container_element_type(0) && p_a.get_container_element_type(0) == p_b.get_container_element_type(0)) { + r_valid = true; + result = p_a; + result.type_source = hard_operation ? GDScriptParser::DataType::ANNOTATED_INFERRED : GDScriptParser::DataType::INFERRED; + return result; + } + } + + Variant::ValidatedOperatorEvaluator op_eval = Variant::get_validated_operator_evaluator(p_operation, a_type, b_type); bool validated = op_eval != nullptr; - GDScriptParser::DataType result; if (validated) { r_valid = true; result.type_source = hard_operation ? GDScriptParser::DataType::ANNOTATED_INFERRED : GDScriptParser::DataType::INFERRED; @@ -5008,8 +5353,8 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ } if (valid && p_target.builtin_type == Variant::ARRAY && p_source.builtin_type == Variant::ARRAY) { // Check the element type. - if (p_target.has_container_element_type() && p_source.has_container_element_type()) { - valid = p_target.get_container_element_type() == p_source.get_container_element_type(); + if (p_target.has_container_element_type(0) && p_source.has_container_element_type(0)) { + valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0); } } return valid; @@ -5186,9 +5531,57 @@ void GDScriptAnalyzer::downgrade_node_type_source(GDScriptParser::Node *p_node) } void GDScriptAnalyzer::mark_lambda_use_self() { - for (GDScriptParser::LambdaNode *lambda : lambda_stack) { + GDScriptParser::LambdaNode *lambda = current_lambda; + while (lambda != nullptr) { lambda->use_self = true; + lambda = lambda->parent_lambda; + } +} + +void GDScriptAnalyzer::resolve_pending_lambda_bodies() { + if (pending_body_resolution_lambdas.is_empty()) { + return; + } + + GDScriptParser::LambdaNode *previous_lambda = current_lambda; + bool previous_static_context = static_context; + + List<GDScriptParser::LambdaNode *> lambdas = pending_body_resolution_lambdas; + pending_body_resolution_lambdas.clear(); + + for (GDScriptParser::LambdaNode *lambda : lambdas) { + current_lambda = lambda; + static_context = lambda->function->is_static; + + resolve_function_body(lambda->function, true); + + int captures_amount = lambda->captures.size(); + if (captures_amount > 0) { + // Create space for lambda parameters. + // At the beginning to not mess with optional parameters. + int param_count = lambda->function->parameters.size(); + lambda->function->parameters.resize(param_count + captures_amount); + for (int i = param_count - 1; i >= 0; i--) { + lambda->function->parameters.write[i + captures_amount] = lambda->function->parameters[i]; + lambda->function->parameters_indices[lambda->function->parameters[i]->identifier->name] = i + captures_amount; + } + + // Add captures as extra parameters at the beginning. + for (int i = 0; i < lambda->captures.size(); i++) { + GDScriptParser::IdentifierNode *capture = lambda->captures[i]; + GDScriptParser::ParameterNode *capture_param = parser->alloc_node<GDScriptParser::ParameterNode>(); + capture_param->identifier = capture; + capture_param->usages = capture->usages; + capture_param->set_datatype(capture->get_datatype()); + + lambda->function->parameters.write[i] = capture_param; + lambda->function->parameters_indices[capture->name] = i; + } + } } + + current_lambda = previous_lambda; + static_context = previous_static_context; } bool GDScriptAnalyzer::class_exists(const StringName &p_class) const { @@ -5247,7 +5640,7 @@ Error GDScriptAnalyzer::analyze() { // Apply annotations. for (GDScriptParser::AnnotationNode *&E : parser->head->annotations) { resolve_annotation(E); - E->apply(parser, parser->head); + E->apply(parser, parser->head, nullptr); } resolve_interface(); |