diff options
Diffstat (limited to 'modules/gdscript')
66 files changed, 649 insertions, 162 deletions
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 22be26fdc1..b5c80d9e2d 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -306,6 +306,9 @@ void GDScript::_get_script_property_list(List<PropertyInfo> *r_list, bool p_incl while (sptr) { Vector<_GDScriptMemberSort> msort; for (const KeyValue<StringName, MemberInfo> &E : sptr->member_indices) { + if (!sptr->members.has(E.key)) { + continue; // Skip base class members. + } _GDScriptMemberSort ms; ms.index = E.value.index; ms.name = E.key; @@ -1648,15 +1651,11 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { } Variant::Type GDScriptInstance::get_property_type(const StringName &p_name, bool *r_is_valid) const { - const GDScript *sptr = script.ptr(); - while (sptr) { - if (sptr->member_indices.has(p_name)) { - if (r_is_valid) { - *r_is_valid = true; - } - return sptr->member_indices[p_name].property_info.type; + if (script->member_indices.has(p_name)) { + if (r_is_valid) { + *r_is_valid = true; } - sptr = sptr->_base; + return script->member_indices[p_name].property_info.type; } if (r_is_valid) { @@ -1732,6 +1731,9 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const Vector<_GDScriptMemberSort> msort; for (const KeyValue<StringName, GDScript::MemberInfo> &F : sptr->member_indices) { + if (!sptr->members.has(F.key)) { + continue; // Skip base class members. + } _GDScriptMemberSort ms; ms.index = F.value.index; ms.name = F.key; @@ -2415,6 +2417,7 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { "return", "match", "while", + "when", // These keywords are not implemented currently, but reserved for (potential) future use. // We highlight them as keywords to make errors easier to understand. "trait", @@ -2448,6 +2451,7 @@ bool GDScriptLanguage::is_control_flow_keyword(String p_keyword) const { p_keyword == "match" || p_keyword == "pass" || p_keyword == "return" || + p_keyword == "when" || p_keyword == "while"; } diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 2b698d75d4..50ccfabcc1 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -94,12 +94,16 @@ class GDScript : public Script { GDScript *_base = nullptr; //fast pointer access GDScript *_owner = nullptr; //for subclasses - HashSet<StringName> members; //members are just indices to the instantiated script. - HashMap<StringName, Variant> constants; + // Members are just indices to the instantiated script. + HashMap<StringName, MemberInfo> member_indices; // Includes member info of all base GDScript classes. + HashSet<StringName> members; // Only members of the current class. + + // Only static variables of the current class. HashMap<StringName, MemberInfo> static_variables_indices; - Vector<Variant> static_variables; + Vector<Variant> static_variables; // Static variable values. + + HashMap<StringName, Variant> constants; HashMap<StringName, GDScriptFunction *> member_functions; - HashMap<StringName, MemberInfo> member_indices; //members are just indices to the instantiated script. HashMap<StringName, Ref<GDScript>> subclasses; HashMap<StringName, MethodInfo> _signals; Dictionary rpc_config; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 55bb99133a..882c246706 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; } @@ -673,11 +673,6 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type 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) { @@ -1676,15 +1671,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) { @@ -1708,7 +1730,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 { @@ -2152,6 +2174,9 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { } 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 { @@ -2219,6 +2244,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); @@ -2552,28 +2581,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.unset_container_element_type(); // 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(expected_type); p_array->set_datatype(array_type); } @@ -2899,19 +2931,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. @@ -2947,7 +2980,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; @@ -2963,10 +2996,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: @@ -2977,21 +3010,27 @@ 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); + // We don't know what type was expected since constructors support overloads. + // TODO: Improve this by checking for matching candidates? + parser->push_warning(p_call->arguments[0], GDScriptWarning::UNSAFE_CALL_ARGUMENT, "1", function_name, "<unknown type>", "Variant"); +#endif + p_call->set_datatype(call_type); + return; } } + List<MethodInfo> constructors; Variant::get_constructor_list(builtin_type, &constructors); bool match = false; @@ -3008,14 +3047,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 } @@ -3023,9 +3062,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() || !is_type_compatible(arg_type, par_type, true)) { + mark_node_unsafe(p_call); + parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), function_name, par_type.to_string(), arg_type.to_string_strict()); + } + } +#endif } match = true; call_type = type_from_property(info.return_val); @@ -3331,8 +3380,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); } } @@ -3820,6 +3869,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident #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()) { @@ -3872,10 +3922,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; @@ -5003,21 +5053,28 @@ 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), 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), 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 } } @@ -5049,7 +5106,7 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier 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) { + } 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; } diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 97e02ac716..f417d323db 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -612,11 +612,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code arguments.push_back(arg); } - if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(call->function_name) != Variant::VARIANT_MAX) { - // Construct a built-in type. - Variant::Type vtype = GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); - - gen->write_construct(result, vtype, arguments); + if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) { + gen->write_construct(result, GDScriptParser::get_builtin_type(call->function_name), arguments); } else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && Variant::has_utility_function(call->function_name)) { // Variant utility function. gen->write_call_utility(result, call->function_name, arguments); @@ -1928,6 +1925,26 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui } } + // If there's a guard, check its condition too. + if (branch->guard_body != nullptr) { + // Do this first so the guard does not run unless the pattern matched. + gen->write_and_left_operand(pattern_result); + + // Don't actually use the block for the guard. + // The binds are already in the locals and we don't want to clear the result of the guard condition before we check the actual match. + GDScriptCodeGenerator::Address guard_result = _parse_expression(codegen, err, static_cast<GDScriptParser::ExpressionNode *>(branch->guard_body->statements[0])); + if (err) { + return err; + } + + gen->write_and_right_operand(guard_result); + gen->write_end_and(pattern_result); + + if (guard_result.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + } + // Check if pattern did match. gen->write_if(pattern_result); @@ -2794,7 +2811,7 @@ Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptP minfo.property_info = prop_info; p_script->member_indices[name] = minfo; - p_script->members.insert(Variant()); + p_script->members.insert(name); } break; case GDScriptParser::ClassNode::Member::FUNCTION: { diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 3e13d1525d..372c212d2b 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -140,7 +140,7 @@ Variant GDScriptFunctionState::_signal_callback(const Variant **p_args, int p_ar if (p_argcount == 0) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; + r_error.expected = 1; return Variant(); } else if (p_argcount == 1) { //noooneee diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 0801582dbd..9fb1030d12 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -52,11 +52,18 @@ #include "editor/editor_settings.h" #endif +// This function is used to determine that a type is "built-in" as opposed to native +// and custom classes. So `Variant::NIL` and `Variant::OBJECT` are excluded: +// `Variant::NIL` - `null` is literal, not a type. +// `Variant::OBJECT` - `Object` should be treated as a class, not as a built-in type. static HashMap<StringName, Variant::Type> builtin_types; Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { - if (builtin_types.is_empty()) { - for (int i = 1; i < Variant::VARIANT_MAX; i++) { - builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i; + if (unlikely(builtin_types.is_empty())) { + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + Variant::Type type = (Variant::Type)i; + if (type != Variant::NIL && type != Variant::OBJECT) { + builtin_types[Variant::get_type_name(type)] = type; + } } } @@ -2035,7 +2042,37 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { push_error(R"(No pattern found for "match" branch.)"); } - if (!consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)")) { + bool has_guard = false; + if (match(GDScriptTokenizer::Token::WHEN)) { + // Pattern guard. + // Create block for guard because it also needs to access the bound variables from patterns, and we don't want to add them to the outer scope. + branch->guard_body = alloc_node<SuiteNode>(); + if (branch->patterns.size() > 0) { + for (const KeyValue<StringName, IdentifierNode *> &E : branch->patterns[0]->binds) { + SuiteNode::Local local(E.value, current_function); + local.type = SuiteNode::Local::PATTERN_BIND; + branch->guard_body->add_local(local); + } + } + + SuiteNode *parent_block = current_suite; + branch->guard_body->parent_block = parent_block; + current_suite = branch->guard_body; + + ExpressionNode *guard = parse_expression(false); + if (guard == nullptr) { + push_error(R"(Expected expression for pattern guard after "when".)"); + } else { + branch->guard_body->statements.append(guard); + } + current_suite = parent_block; + complete_extents(branch->guard_body); + + has_guard = true; + branch->has_wildcard = false; // If it has a guard, the wildcard might still not match. + } + + if (!consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":"%s after "match" %s.)", has_guard ? "" : R"( or "when")", has_guard ? "pattern guard" : "patterns"))) { complete_extents(branch); return nullptr; } @@ -3674,6 +3711,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty { nullptr, nullptr, PREC_NONE }, // PASS, { nullptr, nullptr, PREC_NONE }, // RETURN, { nullptr, nullptr, PREC_NONE }, // MATCH, + { nullptr, nullptr, PREC_NONE }, // WHEN, // Keywords { nullptr, &GDScriptParser::parse_cast, PREC_CAST }, // AS, { nullptr, nullptr, PREC_NONE }, // ASSERT, diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 3bd3696e99..2daadd7e6a 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -149,6 +149,7 @@ public: _FORCE_INLINE_ bool is_hard_type() const { return type_source > INFERRED; } String to_string() const; + _FORCE_INLINE_ String to_string_strict() const { return is_hard_type() ? to_string() : "Variant"; } PropertyInfo to_property_info(const String &p_name) const; _FORCE_INLINE_ void set_container_element_type(const DataType &p_type) { @@ -948,6 +949,7 @@ public: Vector<PatternNode *> patterns; SuiteNode *block = nullptr; bool has_wildcard = false; + SuiteNode *guard_body = nullptr; MatchBranchNode() { type = MATCH_BRANCH; @@ -1530,7 +1532,7 @@ public: bool is_tool() const { return _is_tool; } ClassNode *find_class(const String &p_qualified_name) const; bool has_class(const GDScriptParser::ClassNode *p_class) const; - static Variant::Type get_builtin_type(const StringName &p_type); + static Variant::Type get_builtin_type(const StringName &p_type); // Excluding `Variant::NIL` and `Variant::OBJECT`. CompletionContext get_completion_context() const { return completion_context; } CompletionCall get_completion_call() const { return completion_call; } diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 07f2b8b406..98a3a1268f 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -99,6 +99,7 @@ static const char *token_names[] = { "pass", // PASS, "return", // RETURN, "match", // MATCH, + "when", // WHEN, // Keywords "as", // AS, "assert", // ASSERT, @@ -187,6 +188,7 @@ bool GDScriptTokenizer::Token::is_identifier() const { switch (type) { case IDENTIFIER: case MATCH: // Used in String.match(). + case WHEN: // New keyword, avoid breaking existing code. // Allow constants to be treated as regular identifiers. case CONST_PI: case CONST_INF: @@ -241,6 +243,7 @@ bool GDScriptTokenizer::Token::is_node_name() const { case VAR: case VOID: case WHILE: + case WHEN: case YIELD: return true; default: @@ -531,6 +534,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::annotation() { KEYWORD("void", Token::VOID) \ KEYWORD_GROUP('w') \ KEYWORD("while", Token::WHILE) \ + KEYWORD("when", Token::WHEN) \ KEYWORD_GROUP('y') \ KEYWORD("yield", Token::YIELD) \ KEYWORD_GROUP('I') \ diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index f916407b18..6dd8a98652 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -105,6 +105,7 @@ public: PASS, RETURN, MATCH, + WHEN, // Keywords AS, ASSERT, diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index d85b12b7fe..69a0b42d89 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -45,14 +45,12 @@ #define VALIDATE_ARG_COUNT(m_count) \ if (p_arg_count < m_count) { \ r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \ - r_error.argument = m_count; \ r_error.expected = m_count; \ *r_ret = Variant(); \ return; \ } \ if (p_arg_count > m_count) { \ r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \ - r_error.argument = m_count; \ r_error.expected = m_count; \ *r_ret = Variant(); \ return; \ @@ -119,7 +117,6 @@ struct GDScriptUtilityFunctionsDefinitions { switch (p_arg_count) { case 0: { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; r_error.expected = 1; *r_ret = Variant(); } break; @@ -223,7 +220,6 @@ struct GDScriptUtilityFunctionsDefinitions { } break; default: { r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = 3; r_error.expected = 3; *r_ret = Variant(); @@ -251,6 +247,7 @@ struct GDScriptUtilityFunctionsDefinitions { } else if (p_args[0]->get_type() != Variant::OBJECT) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; + r_error.expected = Variant::OBJECT; *r_ret = Variant(); } else { Object *obj = *p_args[0]; @@ -390,13 +387,13 @@ struct GDScriptUtilityFunctionsDefinitions { static inline void Color8(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { if (p_arg_count < 3) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 3; + r_error.expected = 3; *r_ret = Variant(); return; } if (p_arg_count > 4) { r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = 4; + r_error.expected = 4; *r_ret = Variant(); return; } diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index be18dee2cf..5ecae08f6c 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -519,7 +519,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (p_argcount > _argument_count) { r_err.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; r_err.expected = _argument_count; - call_depth--; return _get_default_variant_for_data_type(return_type); } else if (p_argcount < _argument_count - _default_arg_count) { diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index a0078f84d6..cabac07ef9 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -108,7 +108,7 @@ String GDScriptWarning::get_message() const { return vformat(R"(The value is cast to "%s" but has an unknown type.)", symbols[0]); case UNSAFE_CALL_ARGUMENT: CHECK_SYMBOLS(4); - return vformat(R"*(The argument %s of the function "%s()" requires a the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3]); + return vformat(R"*(The argument %s of the function "%s()" requires the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3]); case UNSAFE_VOID_RETURN: CHECK_SYMBOLS(2); return vformat(R"*(The method "%s()" returns "void" but it's trying to return a call to "%s()" that can't be ensured to also be "void".)*", symbols[0], symbols[1]); diff --git a/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.gd b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.gd new file mode 100644 index 0000000000..87d1b9ea18 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.gd @@ -0,0 +1,7 @@ +# GH-73283 + +class MyClass: + pass + +func test(): + MyClass.not_existing_method() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.out b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.out new file mode 100644 index 0000000000..7340058dd4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Static function "not_existing_method()" not found in base "MyClass". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.gd new file mode 100644 index 0000000000..db3f3f4c72 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.gd @@ -0,0 +1,5 @@ +# GH-82021 + +func test(): + for x: String in [1, 2, 3]: + print(x) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.out new file mode 100644 index 0000000000..0bb654e7e2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot include a value of type "int" as "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.gd new file mode 100644 index 0000000000..fdf22f6843 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.gd @@ -0,0 +1,10 @@ +class A: + func f(_p: Object): + pass + +class B extends A: + func f(_p: Node): + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.out b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.out new file mode 100644 index 0000000000..c6a7e40e8c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f(Object) -> Variant". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.gd new file mode 100644 index 0000000000..e4094f1d76 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.gd @@ -0,0 +1,10 @@ +class A: + func f(_p: Variant): + pass + +class B extends A: + func f(_p: Node): # No `is_type_compatible()` misuse. + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.out b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.out new file mode 100644 index 0000000000..52a6efc6fc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f(Variant) -> Variant". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.gd new file mode 100644 index 0000000000..17663da4f6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.gd @@ -0,0 +1,10 @@ +class A: + func f(_p: int): + pass + +class B extends A: + func f(_p: float): # No implicit conversion. + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.out b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.out new file mode 100644 index 0000000000..7a6207fd45 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f(int) -> Variant". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.gd new file mode 100644 index 0000000000..6dfa75ecbc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.gd @@ -0,0 +1,10 @@ +class A: + func f() -> Node: + return null + +class B extends A: + func f() -> Object: + return null + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.out new file mode 100644 index 0000000000..e680b2bd77 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f() -> Node". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.gd new file mode 100644 index 0000000000..366494b94f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.gd @@ -0,0 +1,10 @@ +class A: + func f() -> Node: + return null + +class B extends A: + func f() -> Variant: # No `is_type_compatible()` misuse. + return null + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.out new file mode 100644 index 0000000000..e680b2bd77 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f() -> Node". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.gd new file mode 100644 index 0000000000..2cb4e7c616 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.gd @@ -0,0 +1,10 @@ +class A: + func f() -> Node: + return null + +class B extends A: + func f() -> void: # No `is_type_compatible()` misuse. + return + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.out new file mode 100644 index 0000000000..e680b2bd77 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f() -> Node". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.gd new file mode 100644 index 0000000000..2cabce46f5 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.gd @@ -0,0 +1,10 @@ +class A: + func f() -> float: + return 0.0 + +class B extends A: + func f() -> int: # No implicit conversion. + return 0 + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.out new file mode 100644 index 0000000000..72f2c493d4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f() -> float". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.gd b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.gd new file mode 100644 index 0000000000..1dcb9fc36a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.gd @@ -0,0 +1,4 @@ +func test(): + match 0: + _ when a == 0: + print("a does not exist") diff --git a/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.out b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.out new file mode 100644 index 0000000000..c5f0a90d6a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Identifier "a" not declared in the current scope. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.gd b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.gd new file mode 100644 index 0000000000..1600c3001f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.gd @@ -0,0 +1,4 @@ +# GH-73213 + +func test(): + print(Object()) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.out b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.out new file mode 100644 index 0000000000..27668fcd48 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid constructor "Object()", use "Object.new()" instead. diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.gd b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.gd new file mode 100644 index 0000000000..a43c233625 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.gd @@ -0,0 +1,20 @@ +class A: + func int_to_variant(_p: int): pass + func node_to_variant(_p: Node): pass + func node_2d_to_node(_p: Node2D): pass + + func variant_to_untyped(_p: Variant): pass + func int_to_untyped(_p: int): pass + func node_to_untyped(_p: Node): pass + +class B extends A: + func int_to_variant(_p: Variant): pass + func node_to_variant(_p: Variant): pass + func node_2d_to_node(_p: Node): pass + + func variant_to_untyped(_p): pass + func int_to_untyped(_p): pass + func node_to_untyped(_p): pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.out b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.gd b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.gd new file mode 100644 index 0000000000..4de50b6731 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.gd @@ -0,0 +1,32 @@ +class A: + func variant_to_int() -> Variant: return 0 + func variant_to_node() -> Variant: return null + func node_to_node_2d() -> Node: return null + + func untyped_to_void(): pass + func untyped_to_variant(): pass + func untyped_to_int(): pass + func untyped_to_node(): pass + + func void_to_untyped() -> void: pass + func variant_to_untyped() -> Variant: return null + func int_to_untyped() -> int: return 0 + func node_to_untyped() -> Node: return null + +class B extends A: + func variant_to_int() -> int: return 0 + func variant_to_node() -> Node: return null + func node_to_node_2d() -> Node2D: return null + + func untyped_to_void() -> void: pass + func untyped_to_variant() -> Variant: return null + func untyped_to_int() -> int: return 0 + func untyped_to_node() -> Node: return null + + func void_to_untyped(): pass + func variant_to_untyped(): pass + func int_to_untyped(): pass + func node_to_untyped(): pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.out b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd index b447180ea8..d0f895d784 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd @@ -23,6 +23,7 @@ func test() -> void: typed = variant() inferred = variant() + @warning_ignore("unsafe_call_argument") # TODO: Hard vs Weak vs Unknown. param_weak(typed) param_typed(typed) param_inferred(typed) diff --git a/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd index 5a413e2015..08e7dc590e 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd @@ -6,10 +6,12 @@ var prop = null func check_arg(arg = null) -> void: if arg != null: + @warning_ignore("unsafe_call_argument") print(check(arg)) func check_recur() -> void: if recur != null: + @warning_ignore("unsafe_call_argument") print(check(recur)) else: recur = 1 @@ -22,11 +24,13 @@ func test() -> void: if prop == null: set('prop', 1) + @warning_ignore("unsafe_call_argument") print(check(prop)) set('prop', null) var loop = null while loop != 2: if loop != null: + @warning_ignore("unsafe_call_argument") print(check(loop)) loop = 1 if loop == null else 2 diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd index 849df0921e..c1776fe1b4 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd +++ b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd @@ -14,4 +14,5 @@ func test(): func do_add_node(): var node = Node.new() node.name = "Node" + @warning_ignore("unsafe_call_argument") add_child(node) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd new file mode 100644 index 0000000000..573060ae0f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd @@ -0,0 +1,37 @@ +func variant_func(x: Variant) -> void: + print(x) + +func int_func(x: int) -> void: + print(x) + +func float_func(x: float) -> void: + print(x) + +# We don't want to execute it because of errors, just analyze. +func no_exec_test(): + var untyped_int = 42 + var untyped_string = "abc" + var variant_int: Variant = 42 + var variant_string: Variant = "abc" + var typed_int: int = 42 + + variant_func(untyped_int) # No warning. + variant_func(untyped_string) # No warning. + variant_func(variant_int) # No warning. + variant_func(variant_string) # No warning. + variant_func(typed_int) # No warning. + + int_func(untyped_int) + int_func(untyped_string) + int_func(variant_int) + int_func(variant_string) + int_func(typed_int) # No warning. + + float_func(untyped_int) + float_func(untyped_string) + float_func(variant_int) + float_func(variant_string) + float_func(typed_int) # No warning. + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out new file mode 100644 index 0000000000..b8fcb67158 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out @@ -0,0 +1,33 @@ +GDTEST_OK +>> WARNING +>> Line: 24 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 25 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 26 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 27 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 30 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. +>> WARNING +>> Line: 31 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. +>> WARNING +>> Line: 32 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. +>> WARNING +>> Line: 33 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. diff --git a/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.gd b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.gd new file mode 100644 index 0000000000..d88b4a37c4 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.gd @@ -0,0 +1,5 @@ +func test(): + var a = 0 + match a: + 0 when a = 1: + print("assignment not allowed on pattern guard") diff --git a/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.out b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.out new file mode 100644 index 0000000000..e8f9130706 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Assignment is not allowed inside an expression. diff --git a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd index 7e1982597c..0c8a5d1367 100644 --- a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd +++ b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd @@ -14,3 +14,7 @@ func test(): var TAU = "TAU" print(TAU) + + # New keyword for pattern guards. + var when = "when" + print(when) diff --git a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out index aae2ae13d5..8ac8e92ef7 100644 --- a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out +++ b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out @@ -4,3 +4,4 @@ PI INF NAN TAU +when diff --git a/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd b/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd index f04f4de08d..19f6e08285 100644 --- a/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd +++ b/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd @@ -3,27 +3,32 @@ extends Node func test(): var child = Node.new() child.name = "Child" + @warning_ignore("unsafe_call_argument") add_child(child) child.owner = self var hey = Node.new() hey.name = "Hey" + @warning_ignore("unsafe_call_argument") child.add_child(hey) hey.owner = self hey.unique_name_in_owner = true var fake_hey = Node.new() fake_hey.name = "Hey" + @warning_ignore("unsafe_call_argument") add_child(fake_hey) fake_hey.owner = self var sub_child = Node.new() sub_child.name = "SubChild" + @warning_ignore("unsafe_call_argument") hey.add_child(sub_child) sub_child.owner = self var howdy = Node.new() howdy.name = "Howdy" + @warning_ignore("unsafe_call_argument") sub_child.add_child(howdy) howdy.owner = self howdy.unique_name_in_owner = true diff --git a/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd index 8ba558e91d..3d9404b20b 100644 --- a/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd +++ b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd @@ -5,9 +5,11 @@ func test(): # Create the required node structure. var hello = Node.new() hello.name = "Hello" + @warning_ignore("unsafe_call_argument") add_child(hello) var world = Node.new() world.name = "World" + @warning_ignore("unsafe_call_argument") hello.add_child(world) # All the ways of writing node paths below with the `$` operator are valid. diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd b/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd index df6001c7e2..f16c768f7f 100644 --- a/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd +++ b/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd @@ -26,6 +26,7 @@ func test(): if true: (v as Callable).call() print() + @warning_ignore("unsafe_call_argument") other(v) print() diff --git a/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd index 59cdc7d6c2..31de73813f 100644 --- a/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd +++ b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd @@ -2,4 +2,5 @@ func foo(x): return x + 1 func test(): + @warning_ignore("unsafe_call_argument") print(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(0))))))))))))))))))))))))) diff --git a/modules/gdscript/tests/scripts/parser/features/super.gd b/modules/gdscript/tests/scripts/parser/features/super.gd index f5ae2a74a7..33accd92a9 100644 --- a/modules/gdscript/tests/scripts/parser/features/super.gd +++ b/modules/gdscript/tests/scripts/parser/features/super.gd @@ -36,6 +36,7 @@ class SayNothing extends Say: print("howdy, see above") func say(name): + @warning_ignore("unsafe_call_argument") super(name + " super'd") print(prefix, " say nothing... or not? ", name) diff --git a/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd index 523959a016..20cc0cee2e 100644 --- a/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd +++ b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd @@ -29,6 +29,7 @@ func test(): const d = 1.1 _process(d) + @warning_ignore("unsafe_call_argument") print(is_equal_approx(ㄥ, PI + (d * PI))) func _process(Δ: float) -> void: diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd index 58b4df5a79..bc899a3a6f 100644 --- a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd +++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd @@ -21,6 +21,12 @@ func test(): var elem := e prints(var_to_str(e), var_to_str(elem)) + # GH-82021 + print("Test implicitly typed array literal.") + for e: float in [100, 200, 300]: + var elem := e + prints(var_to_str(e), var_to_str(elem)) + print("Test String-keys dictionary.") var d1 := {a = 1, b = 2, c = 3} for k: StringName in d1: diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out index f67f7d89d5..eeebdc4be5 100644 --- a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out +++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out @@ -15,6 +15,10 @@ Test typed int array. 10.0 10.0 20.0 20.0 30.0 30.0 +Test implicitly typed array literal. +100.0 100.0 +200.0 200.0 +300.0 300.0 Test String-keys dictionary. &"a" &"a" &"b" &"b" diff --git a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd new file mode 100644 index 0000000000..4cb51f8512 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd @@ -0,0 +1,71 @@ +var global := 0 + +func test(): + var a = 0 + var b = 1 + + match a: + 0 when b == 0: + print("does not run" if true else "") + 0 when b == 1: + print("guards work") + _: + print("does not run") + + match a: + var a_bind when b == 0: + prints("a is", a_bind, "and b is 0") + var a_bind when b == 1: + prints("a is", a_bind, "and b is 1") + _: + print("does not run") + + match a: + var a_bind when a_bind < 0: + print("a is less than zero") + var a_bind when a_bind == 0: + print("a is equal to zero") + _: + print("a is more than zero") + + match [1, 2, 3]: + [1, 2, var element] when element == 0: + print("does not run") + [1, 2, var element] when element == 3: + print("3rd element is 3") + + match a: + _ when b == 0: + print("does not run") + _ when b == 1: + print("works with wildcard too.") + _: + print("does not run") + + match a: + 0, 1 when b == 0: + print("does not run") + 0, 1 when b == 1: + print("guard with multiple patterns") + _: + print("does not run") + + match a: + 0 when b == 0: + print("does not run") + 0: + print("regular pattern after guard mismatch") + + match a: + 1 when side_effect(): + print("should not run the side effect call") + 0 when side_effect(): + print("will run the side effect call, but not this") + _: + assert(global == 1) + print("side effect only ran once") + +func side_effect(): + print("side effect") + global += 1 + return false diff --git a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.out b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.out new file mode 100644 index 0000000000..452d1ff4bf --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.out @@ -0,0 +1,10 @@ +GDTEST_OK +guards work +a is 0 and b is 1 +a is equal to zero +3rd element is 3 +works with wildcard too. +guard with multiple patterns +regular pattern after guard mismatch +side effect +side effect only ran once diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd new file mode 100644 index 0000000000..d0cbeeab85 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd @@ -0,0 +1,45 @@ +# GH-82169 + +const Utils = preload("../../utils.notest.gd") + +class A: + static var test_static_var_a1 + static var test_static_var_a2 + var test_var_a1 + var test_var_a2 + static func test_static_func_a1(): pass + static func test_static_func_a2(): pass + func test_func_a1(): pass + func test_func_a2(): pass + signal test_signal_a1() + signal test_signal_a2() + +class B extends A: + static var test_static_var_b1 + static var test_static_var_b2 + var test_var_b1 + var test_var_b2 + static func test_static_func_b1(): pass + static func test_static_func_b2(): pass + func test_func_b1(): pass + func test_func_b2(): pass + signal test_signal_b1() + signal test_signal_b2() + +func test(): + var b := B.new() + for property in (B as GDScript).get_property_list(): + if str(property.name).begins_with("test_"): + print(Utils.get_property_signature(property, true)) + print("---") + for property in b.get_property_list(): + if str(property.name).begins_with("test_"): + print(Utils.get_property_signature(property)) + print("---") + for method in b.get_method_list(): + if str(method.name).begins_with("test_"): + print(Utils.get_method_signature(method)) + print("---") + for method in b.get_signal_list(): + if str(method.name).begins_with("test_"): + print(Utils.get_method_signature(method, true)) diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.out b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.out new file mode 100644 index 0000000000..f294b66ef9 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.out @@ -0,0 +1,24 @@ +GDTEST_OK +static var test_static_var_a1: Variant +static var test_static_var_a2: Variant +static var test_static_var_b1: Variant +static var test_static_var_b2: Variant +--- +var test_var_b1: Variant +var test_var_b2: Variant +var test_var_a1: Variant +var test_var_a2: Variant +--- +static func test_static_func_b1() -> void +static func test_static_func_b2() -> void +func test_func_b1() -> void +func test_func_b2() -> void +static func test_static_func_a1() -> void +static func test_static_func_a2() -> void +func test_func_a1() -> void +func test_func_a2() -> void +--- +signal test_signal_b1() +signal test_signal_b2() +signal test_signal_a1() +signal test_signal_a2() diff --git a/modules/gdscript/tests/scripts/runtime/features/object_constructor.gd b/modules/gdscript/tests/scripts/runtime/features/object_constructor.gd new file mode 100644 index 0000000000..b875efef56 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/object_constructor.gd @@ -0,0 +1,6 @@ +# GH-73213 + +func test(): + var object := Object.new() # Not `Object()`. + print(object.get_class()) + object.free() diff --git a/modules/gdscript/tests/scripts/runtime/features/object_constructor.out b/modules/gdscript/tests/scripts/runtime/features/object_constructor.out new file mode 100644 index 0000000000..3673881576 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/object_constructor.out @@ -0,0 +1,2 @@ +GDTEST_OK +Object diff --git a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd index 2f55059334..fd1460a48f 100644 --- a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd +++ b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd @@ -12,6 +12,7 @@ func test(): print("end") func test_construct(v, f): + @warning_ignore("unsafe_call_argument") Vector2(v, v) # Built-in type construct. assert(not f) # Test unary operator reading from `nil`. diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd index 8da8bb7e53..7fa76ca4b0 100644 --- a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd +++ b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd @@ -44,6 +44,7 @@ func test(): @warning_ignore("unsafe_method_access") var path = get_script().get_path().get_base_dir() + @warning_ignore("unsafe_call_argument") var other = load(path + "/static_variables_load.gd") prints("load.perm:", other.perm) diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.gd b/modules/gdscript/tests/scripts/runtime/features/stringify.gd index fead2df854..1e66d8f34a 100644 --- a/modules/gdscript/tests/scripts/runtime/features/stringify.gd +++ b/modules/gdscript/tests/scripts/runtime/features/stringify.gd @@ -24,7 +24,8 @@ func test(): print(StringName("hello")) print(NodePath("hello/world")) var node := Node.new() - print(RID(node)) + @warning_ignore("unsafe_call_argument") + print(RID(node)) # TODO: Why is the constructor (or implicit cast) not documented? print(node.get_name) print(node.property_list_changed) node.free() diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd index fb20817117..781843b8e2 100644 --- a/modules/gdscript/tests/scripts/utils.notest.gd +++ b/modules/gdscript/tests/scripts/utils.notest.gd @@ -17,7 +17,7 @@ static func get_type(property: Dictionary, is_return: bool = false) -> String: TYPE_OBJECT: if not str(property.class_name).is_empty(): return property.class_name - return variant_get_type_name(property.type) + return type_string(property.type) static func get_property_signature(property: Dictionary, is_static: bool = false) -> String: @@ -66,88 +66,6 @@ static func get_method_signature(method: Dictionary, is_signal: bool = false) -> return result -static func variant_get_type_name(type: Variant.Type) -> String: - match type: - TYPE_NIL: - return "Nil" # `Nil` in core, `null` in GDScript. - TYPE_BOOL: - return "bool" - TYPE_INT: - return "int" - TYPE_FLOAT: - return "float" - TYPE_STRING: - return "String" - TYPE_VECTOR2: - return "Vector2" - TYPE_VECTOR2I: - return "Vector2i" - TYPE_RECT2: - return "Rect2" - TYPE_RECT2I: - return "Rect2i" - TYPE_VECTOR3: - return "Vector3" - TYPE_VECTOR3I: - return "Vector3i" - TYPE_TRANSFORM2D: - return "Transform2D" - TYPE_VECTOR4: - return "Vector4" - TYPE_VECTOR4I: - return "Vector4i" - TYPE_PLANE: - return "Plane" - TYPE_QUATERNION: - return "Quaternion" - TYPE_AABB: - return "AABB" - TYPE_BASIS: - return "Basis" - TYPE_TRANSFORM3D: - return "Transform3D" - TYPE_PROJECTION: - return "Projection" - TYPE_COLOR: - return "Color" - TYPE_STRING_NAME: - return "StringName" - TYPE_NODE_PATH: - return "NodePath" - TYPE_RID: - return "RID" - TYPE_OBJECT: - return "Object" - TYPE_CALLABLE: - return "Callable" - TYPE_SIGNAL: - return "Signal" - TYPE_DICTIONARY: - return "Dictionary" - TYPE_ARRAY: - return "Array" - TYPE_PACKED_BYTE_ARRAY: - return "PackedByteArray" - TYPE_PACKED_INT32_ARRAY: - return "PackedInt32Array" - TYPE_PACKED_INT64_ARRAY: - return "PackedInt64Array" - TYPE_PACKED_FLOAT32_ARRAY: - return "PackedFloat32Array" - TYPE_PACKED_FLOAT64_ARRAY: - return "PackedFloat64Array" - TYPE_PACKED_STRING_ARRAY: - return "PackedStringArray" - TYPE_PACKED_VECTOR2_ARRAY: - return "PackedVector2Array" - TYPE_PACKED_VECTOR3_ARRAY: - return "PackedVector3Array" - TYPE_PACKED_COLOR_ARRAY: - return "PackedColorArray" - push_error("Argument `type` is invalid. Use `TYPE_*` constants.") - return "<invalid type>" - - static func get_property_hint_name(hint: PropertyHint) -> String: match hint: PROPERTY_HINT_NONE: diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp index b86a8b3cb1..467bedc4b2 100644 --- a/modules/gdscript/tests/test_gdscript.cpp +++ b/modules/gdscript/tests/test_gdscript.cpp @@ -223,6 +223,16 @@ void test(TestType p_type) { // Initialize the language for the test routine. init_language(fa->get_path_absolute().get_base_dir()); + // Load global classes. + TypedArray<Dictionary> script_classes = ProjectSettings::get_singleton()->get_global_class_list(); + for (int i = 0; i < script_classes.size(); i++) { + Dictionary c = script_classes[i]; + if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base")) { + continue; + } + ScriptServer::add_global_class(c["class"], c["base"], c["language"], c["path"]); + } + Vector<uint8_t> buf; uint64_t flen = fa->get_length(); buf.resize(flen + 1); |