From 31e0ae2012577bce11c23064c5d743a617b69497 Mon Sep 17 00:00:00 2001 From: Dmitrii Maganov Date: Sun, 22 Jan 2023 10:32:05 +0200 Subject: [PATCH] GDScript: Fix constant conversions --- doc/classes/ProjectSettings.xml | 7 +- modules/gdscript/gdscript_analyzer.cpp | 190 ++++++++++-------- modules/gdscript/gdscript_analyzer.h | 5 +- modules/gdscript/gdscript_warning.cpp | 9 +- modules/gdscript/gdscript_warning.h | 3 +- .../errors/assymetric_assignment_bad.gd | 3 + .../errors/assymetric_assignment_bad.out | 2 + .../analyzer/errors/cast_enum_bad_enum.out | 2 - .../analyzer/errors/cast_enum_bad_int.out | 2 - ..._class_var_assign_with_wrong_enum_type.out | 2 +- ...um_class_var_init_with_wrong_enum_type.out | 2 +- .../enum_function_parameter_wrong_type.out | 2 +- .../enum_function_return_wrong_type.out | 2 +- ..._var_assign_outer_with_wrong_enum_type.out | 2 +- ..._local_var_assign_with_wrong_enum_type.out | 2 +- ...um_local_var_init_with_wrong_enum_type.out | 2 +- .../enum_preload_unnamed_assign_to_named.out | 2 +- .../errors/enum_unnamed_assign_to_named.out | 2 +- .../analyzer/errors/lambda_wrong_return.out | 2 +- .../analyzer/errors/preload_enum_error.out | 2 +- .../features/assymetric_assignment_good.gd | 16 ++ .../features/assymetric_assignment_good.out | 2 + .../analyzer/features/const_conversions.gd | 24 +++ .../analyzer/features/const_conversions.out | 2 + .../cast_enum_bad_enum.gd | 0 .../analyzer/warnings/cast_enum_bad_enum.out | 6 + .../{errors => warnings}/cast_enum_bad_int.gd | 0 .../analyzer/warnings/cast_enum_bad_int.out | 6 + .../enum_assign_int_without_casting.out | 8 +- 29 files changed, 204 insertions(+), 105 deletions(-) create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/assymetric_assignment_bad.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/assymetric_assignment_bad.out delete mode 100644 modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.out delete mode 100644 modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/const_conversions.out rename modules/gdscript/tests/scripts/analyzer/{errors => warnings}/cast_enum_bad_enum.gd (100%) create mode 100644 modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out rename modules/gdscript/tests/scripts/analyzer/{errors => warnings}/cast_enum_bad_int.gd (100%) create mode 100644 modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 430f1c60fd5..fa381adbb38 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -408,8 +408,11 @@ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a ternary operator may emit values with incompatible types. - - When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when trying to assign an integer to a variable that expects an enum value. + + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when trying to use an integer as an enum without an explicit cast. + + + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when trying to use an integer as an enum when there is no matching enum member for that numeric value. When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when dividing an integer by another integer (the decimal part will be discarded). diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 8c3521c530f..8fcfa3418f4 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -160,19 +160,6 @@ static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) { return type; } -static StringName enum_get_value_name(const GDScriptParser::DataType p_type, int64_t p_val) { - // Check that an enum has a given value, not key. - // Make sure that implicit conversion to int64_t is sensible before calling! - HashMap::ConstIterator i = p_type.enum_values.begin(); - while (i) { - if (i->value == p_val) { - return i->key; - } - ++i; - } - return StringName(); -} - bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_member) { if (p_class->members_indices.has(p_member_name)) { int index = p_class->members_indices[p_member_name]; @@ -1596,6 +1583,9 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } } + if (has_specified_type && p_assignable->initializer->is_constant) { + update_const_expression_builtin_type(p_assignable->initializer, specified_type, "assign"); + } GDScriptParser::DataType initializer_type = p_assignable->initializer->get_datatype(); if (p_assignable->infer_datatype) { @@ -1630,7 +1620,7 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi downgrade_node_type_source(p_assignable->initializer); } } else if (!is_type_compatible(specified_type, initializer_type, true, p_assignable->initializer)) { - if (!is_constant && is_type_compatible(initializer_type, specified_type, true, p_assignable->initializer)) { + if (!is_constant && is_type_compatible(initializer_type, specified_type)) { mark_node_unsafe(p_assignable->initializer); p_assignable->use_conversion_assign = true; } else { @@ -1976,6 +1966,9 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) { if (has_expected_type && expected_type.is_hard_type() && expected_type.kind == GDScriptParser::DataType::BUILTIN && expected_type.builtin_type == Variant::NIL) { push_error("A void function cannot return a value.", p_return); } + 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"); + } result = p_return->return_value->get_datatype(); } else { // Return type is null by default. @@ -2116,6 +2109,68 @@ void GDScriptAnalyzer::reduce_array(GDScriptParser::ArrayNode *p_array) { p_array->set_datatype(arr_type); } +#ifdef DEBUG_ENABLED +static bool enum_has_value(const GDScriptParser::DataType p_type, int64_t p_value) { + for (const KeyValue &E : p_type.enum_values) { + if (E.value == p_value) { + return true; + } + } + return false; +} +#endif + +void GDScriptAnalyzer::update_const_expression_builtin_type(GDScriptParser::ExpressionNode *p_expression, const GDScriptParser::DataType &p_type, const char *p_usage, bool p_is_cast) { + if (p_expression->get_datatype() == p_type) { + return; + } + if (p_type.kind != GDScriptParser::DataType::BUILTIN && p_type.kind != GDScriptParser::DataType::ENUM) { + return; + } + + GDScriptParser::DataType expression_type = p_expression->get_datatype(); + bool is_enum_cast = p_is_cast && p_type.kind == GDScriptParser::DataType::ENUM && p_type.is_meta_type == false && expression_type.builtin_type == Variant::INT; + if (!is_enum_cast && !is_type_compatible(p_type, expression_type, true, p_expression)) { + push_error(vformat(R"(Cannot %s a value of type "%s" as "%s".)", p_usage, expression_type.to_string(), p_type.to_string()), p_expression); + return; + } + + GDScriptParser::DataType value_type = type_from_variant(p_expression->reduced_value, p_expression); + if (expression_type.is_variant() && !is_enum_cast && !is_type_compatible(p_type, value_type, true, p_expression)) { + push_error(vformat(R"(Cannot %s a value of type "%s" as "%s".)", p_usage, value_type.to_string(), p_type.to_string()), p_expression); + return; + } + +#ifdef DEBUG_ENABLED + if (p_type.kind == GDScriptParser::DataType::ENUM && value_type.builtin_type == Variant::INT && !enum_has_value(p_type, p_expression->reduced_value)) { + parser->push_warning(p_expression, GDScriptWarning::INT_AS_ENUM_WITHOUT_MATCH, p_usage, p_expression->reduced_value.stringify(), p_type.to_string()); + } +#endif + + if (value_type.builtin_type == p_type.builtin_type) { + p_expression->set_datatype(p_type); + return; + } + + Variant converted_to; + const Variant *converted_from = &p_expression->reduced_value; + Callable::CallError call_error; + Variant::construct(p_type.builtin_type, converted_to, &converted_from, 1, call_error); + if (call_error.error) { + push_error(vformat(R"(Failed to convert a value of type "%s" to "%s".)", value_type.to_string(), p_type.to_string()), p_expression); + return; + } + +#ifdef DEBUG_ENABLED + if (p_type.builtin_type == Variant::INT && value_type.builtin_type == Variant::FLOAT) { + parser->push_warning(p_expression, GDScriptWarning::NARROWING_CONVERSION); + } +#endif + + p_expression->reduced_value = converted_to; + p_expression->set_datatype(p_type); +} + // 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(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal) { @@ -2182,6 +2237,10 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig update_array_literal_element_type(assignee_type, static_cast(p_assignment->assigned_value)); } + if (p_assignment->operation == GDScriptParser::AssignmentNode::OP_NONE && assignee_type.is_hard_type() && p_assignment->assigned_value->is_constant) { + update_const_expression_builtin_type(p_assignment->assigned_value, assignee_type, "assign"); + } + GDScriptParser::DataType assigned_value_type = p_assignment->assigned_value->get_datatype(); bool assignee_is_variant = assignee_type.is_variant(); @@ -2242,7 +2301,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig // non-variant assignee and incompatible result mark_node_unsafe(p_assignment); if (assignee_is_hard) { - if (is_type_compatible(op_type, assignee_type, true, p_assignment->assigned_value)) { + if (is_type_compatible(op_type, assignee_type)) { // hard non-variant assignee and maybe compatible result p_assignment->use_conversion_assign = true; } else { @@ -2358,7 +2417,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o GDScriptParser::DataType test_type = right_type; test_type.is_meta_type = false; - if (!is_type_compatible(test_type, left_type, false)) { + if (!is_type_compatible(test_type, left_type)) { push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)"), p_binary_op->left_operand); p_binary_op->reduced_value = false; } else { @@ -2379,7 +2438,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o GDScriptParser::DataType test_type = right_type; test_type.is_meta_type = false; - if (!is_type_compatible(test_type, left_type, false) && !is_type_compatible(left_type, test_type, false)) { + if (!is_type_compatible(test_type, left_type) && !is_type_compatible(left_type, test_type)) { if (left_type.is_hard_type()) { push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", left_type.to_string(), test_type.to_string()), p_binary_op->left_operand); } else { @@ -2555,6 +2614,11 @@ 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++) { + if (p_call->arguments[i]->is_constant) { + update_const_expression_builtin_type(p_call->arguments[i], type_from_property(info.arguments[i], true), "pass"); + } + } match = true; call_type = type_from_property(info.return_val); break; @@ -2851,67 +2915,39 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { } p_cast->set_datatype(cast_type); + if (p_cast->operand->is_constant) { + update_const_expression_builtin_type(p_cast->operand, cast_type, "cast", true); + if (cast_type.is_variant() || p_cast->operand->get_datatype() == cast_type) { + p_cast->is_constant = true; + p_cast->reduced_value = p_cast->operand->reduced_value; + } + } if (!cast_type.is_variant()) { GDScriptParser::DataType op_type = p_cast->operand->get_datatype(); - if (!op_type.is_variant()) { + if (op_type.is_variant() || !op_type.is_hard_type()) { + mark_node_unsafe(p_cast); +#ifdef DEBUG_ENABLED + if (op_type.is_variant() && !op_type.is_hard_type()) { + parser->push_warning(p_cast, GDScriptWarning::UNSAFE_CAST, cast_type.to_string()); + } +#endif + } else { bool valid = false; - bool more_informative_error = false; - if (op_type.kind == GDScriptParser::DataType::ENUM && cast_type.kind == GDScriptParser::DataType::ENUM) { - // Enum casts are compatible when value from operand exists in target enum - if (p_cast->operand->is_constant && p_cast->operand->reduced) { - if (enum_get_value_name(cast_type, p_cast->operand->reduced_value) != StringName()) { - valid = true; - } else { - valid = false; - more_informative_error = true; - push_error(vformat(R"(Invalid cast. Enum "%s" does not have value corresponding to "%s.%s" (%d).)", - cast_type.to_string(), op_type.enum_type, - enum_get_value_name(op_type, p_cast->operand->reduced_value), // Can never be null - p_cast->operand->reduced_value.operator uint64_t()), - p_cast->cast_type); - } - } else { - // Can't statically tell whether int has a corresponding enum value. Valid but dangerous! - mark_node_unsafe(p_cast); - valid = true; - } - } else if (op_type.kind == GDScriptParser::DataType::BUILTIN && op_type.builtin_type == Variant::INT && cast_type.kind == GDScriptParser::DataType::ENUM) { - // Int assignment to enum not valid when exact int assigned is known but is not an enum value - if (p_cast->operand->is_constant && p_cast->operand->reduced) { - if (enum_get_value_name(cast_type, p_cast->operand->reduced_value) != StringName()) { - valid = true; - } else { - valid = false; - more_informative_error = true; - push_error(vformat(R"(Invalid cast. Enum "%s" does not have enum value %d.)", cast_type.to_string(), p_cast->operand->reduced_value.operator uint64_t()), p_cast->cast_type); - } - } else { - // Can't statically tell whether int has a corresponding enum value. Valid but dangerous! - mark_node_unsafe(p_cast); - valid = true; - } + if (op_type.builtin_type == Variant::INT && cast_type.kind == GDScriptParser::DataType::ENUM) { + mark_node_unsafe(p_cast); + valid = true; } else if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) { valid = Variant::can_convert(op_type.builtin_type, cast_type.builtin_type); } else if (op_type.kind != GDScriptParser::DataType::BUILTIN && cast_type.kind != GDScriptParser::DataType::BUILTIN) { valid = is_type_compatible(cast_type, op_type) || is_type_compatible(op_type, cast_type); } - if (!valid && !more_informative_error) { + if (!valid) { push_error(vformat(R"(Invalid cast. Cannot convert from "%s" to "%s".)", op_type.to_string(), cast_type.to_string()), p_cast->cast_type); } } - } else { - mark_node_unsafe(p_cast); } -#ifdef DEBUG_ENABLED - if (p_cast->operand->get_datatype().is_variant()) { - parser->push_warning(p_cast, GDScriptWarning::UNSAFE_CAST, cast_type.to_string()); - mark_node_unsafe(p_cast); - } -#endif - - // TODO: Perform cast on constants. } void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) { @@ -4210,26 +4246,22 @@ bool GDScriptAnalyzer::function_signature_from_info(const MethodInfo &p_info, GD return true; } -bool GDScriptAnalyzer::validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call) { +void GDScriptAnalyzer::validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call) { List arg_types; for (const PropertyInfo &E : p_method.arguments) { arg_types.push_back(type_from_property(E, true)); } - return validate_call_arg(arg_types, p_method.default_arguments.size(), (p_method.flags & METHOD_FLAG_VARARG) != 0, p_call); + validate_call_arg(arg_types, p_method.default_arguments.size(), (p_method.flags & METHOD_FLAG_VARARG) != 0, p_call); } -bool GDScriptAnalyzer::validate_call_arg(const List &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call) { - bool valid = true; - +void GDScriptAnalyzer::validate_call_arg(const List &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call) { if (p_call->arguments.size() < p_par_types.size() - p_default_args_count) { push_error(vformat(R"*(Too few arguments for "%s()" call. Expected at least %d but received %d.)*", p_call->function_name, p_par_types.size() - p_default_args_count, p_call->arguments.size()), p_call); - valid = false; } if (!p_is_vararg && p_call->arguments.size() > p_par_types.size()) { push_error(vformat(R"*(Too many arguments for "%s()" call. Expected at most %d but received %d.)*", p_call->function_name, p_par_types.size(), p_call->arguments.size()), p_call->arguments[p_par_types.size()]); - valid = false; } for (int i = 0; i < p_call->arguments.size(); i++) { @@ -4238,9 +4270,13 @@ bool GDScriptAnalyzer::validate_call_arg(const List &p break; } GDScriptParser::DataType par_type = p_par_types[i]; + + if (par_type.is_hard_type() && p_call->arguments[i]->is_constant) { + update_const_expression_builtin_type(p_call->arguments[i], par_type, "pass"); + } GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype(); - if (arg_type.is_variant() && !(par_type.is_hard_type() && par_type.is_variant())) { + if ((arg_type.is_variant() || !arg_type.is_hard_type()) && !(par_type.is_hard_type() && par_type.is_variant())) { // Argument can be anything, so this is unsafe. mark_node_unsafe(p_call->arguments[i]); } else if (par_type.is_hard_type() && !is_type_compatible(par_type, arg_type, true)) { @@ -4250,17 +4286,13 @@ bool GDScriptAnalyzer::validate_call_arg(const List &p 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]); - valid = false; } #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); - } + } 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); #endif } } - return valid; } #ifdef DEBUG_ENABLED @@ -4412,7 +4444,7 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) { #ifdef DEBUG_ENABLED if (p_source_node) { - parser->push_warning(p_source_node, GDScriptWarning::INT_ASSIGNED_TO_ENUM); + parser->push_warning(p_source_node, GDScriptWarning::INT_AS_ENUM_WITHOUT_CAST); } #endif return true; diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 5397be33f06..cd2c4c6569d 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -112,10 +112,11 @@ class GDScriptAnalyzer { GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source); bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); - bool validate_call_arg(const List &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call); - bool validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call); + void validate_call_arg(const List &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call); + void validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call); GDScriptParser::DataType 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); GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source); + void update_const_expression_builtin_type(GDScriptParser::ExpressionNode *p_expression, const GDScriptParser::DataType &p_type, const char *p_usage, bool p_is_cast = false); void update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal); bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr); void push_error(const String &p_message, const GDScriptParser::Node *p_origin = nullptr); diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index a6cbb7f6aec..024fed85170 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -148,9 +148,13 @@ String GDScriptWarning::get_message() const { CHECK_SYMBOLS(3); return vformat(R"(The %s '%s' has the same name as a %s.)", symbols[0], symbols[1], symbols[2]); } - case INT_ASSIGNED_TO_ENUM: { + case INT_AS_ENUM_WITHOUT_CAST: { return "Integer used when an enum value is expected. If this is intended cast the integer to the enum type."; } + case INT_AS_ENUM_WITHOUT_MATCH: { + CHECK_SYMBOLS(3); + return vformat(R"(Cannot %s %s as Enum "%s": no enum member has matching value.)", symbols[0], symbols[1], symbols[2]); + } break; case STATIC_CALLED_ON_INSTANCE: { CHECK_SYMBOLS(2); return vformat(R"(The function '%s()' is a static function but was called from an instance. Instead, it should be directly called from the type: '%s.%s()'.)", symbols[0], symbols[1], symbols[0]); @@ -221,7 +225,8 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "REDUNDANT_AWAIT", "EMPTY_FILE", "SHADOWED_GLOBAL_IDENTIFIER", - "INT_ASSIGNED_TO_ENUM", + "INT_AS_ENUM_WITHOUT_CAST", + "INT_AS_ENUM_WITHOUT_MATCH", "STATIC_CALLED_ON_INSTANCE", "CONFUSABLE_IDENTIFIER", }; diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index b485f02b9c5..7492972c1ac 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -76,7 +76,8 @@ public: REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine). EMPTY_FILE, // A script file is empty. SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable. - INT_ASSIGNED_TO_ENUM, // An integer value was assigned to an enum-typed variable without casting. + INT_AS_ENUM_WITHOUT_CAST, // An integer value was used as an enum value without casting. + INT_AS_ENUM_WITHOUT_MATCH, // An integer value was used as an enum value without matching enum member. STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself. CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e"). WARNING_MAX, diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assymetric_assignment_bad.gd b/modules/gdscript/tests/scripts/analyzer/errors/assymetric_assignment_bad.gd new file mode 100644 index 00000000000..1b22d173c70 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assymetric_assignment_bad.gd @@ -0,0 +1,3 @@ +func test(): + var var_color: String = Color.RED + print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assymetric_assignment_bad.out b/modules/gdscript/tests/scripts/analyzer/errors/assymetric_assignment_bad.out new file mode 100644 index 00000000000..cc4b1d86bfd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assymetric_assignment_bad.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a value of type "Color" as "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.out b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.out deleted file mode 100644 index 3a8d2a205a8..00000000000 --- a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.out +++ /dev/null @@ -1,2 +0,0 @@ -GDTEST_ANALYZER_ERROR -Invalid cast. Enum "cast_enum_bad_enum.gd::MyEnum" does not have value corresponding to "MyOtherEnum.OTHER_ENUM_VALUE_3" (2). diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.out b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.out deleted file mode 100644 index bc0d8b7834c..00000000000 --- a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.out +++ /dev/null @@ -1,2 +0,0 @@ -GDTEST_ANALYZER_ERROR -Invalid cast. Enum "cast_enum_bad_int.gd::MyEnum" does not have enum value 2. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out index 02c46335865..84958f1aa22 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Value of type "enum_class_var_assign_with_wrong_enum_type.gd::MyOtherEnum" cannot be assigned to a variable of type "enum_class_var_assign_with_wrong_enum_type.gd::MyEnum". +Cannot assign a value of type "enum_class_var_assign_with_wrong_enum_type.gd::MyOtherEnum" as "enum_class_var_assign_with_wrong_enum_type.gd::MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out index 441cccbf7b0..e294f3496a6 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot assign a value of type enum_class_var_init_with_wrong_enum_type.gd::MyOtherEnum to variable "class_var" with specified type enum_class_var_init_with_wrong_enum_type.gd::MyEnum. +Cannot assign a value of type "enum_class_var_init_with_wrong_enum_type.gd::MyOtherEnum" as "enum_class_var_init_with_wrong_enum_type.gd::MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out index e85f7d6f9f1..a91189e2ddb 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Invalid argument for "enum_func()" function: argument 1 should be "enum_function_parameter_wrong_type.gd::MyEnum" but is "enum_function_parameter_wrong_type.gd::MyOtherEnum". +Cannot pass a value of type "enum_function_parameter_wrong_type.gd::MyOtherEnum" as "enum_function_parameter_wrong_type.gd::MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out index f7ea3267fac..6b4eba37401 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot return value of type "enum_function_return_wrong_type.gd::MyOtherEnum" because the function return type is "enum_function_return_wrong_type.gd::MyEnum". +Cannot return a value of type "enum_function_return_wrong_type.gd::MyOtherEnum" as "enum_function_return_wrong_type.gd::MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out index 38df5a0cd84..616358bb613 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Value of type "enum_local_var_assign_outer_with_wrong_enum_type.gd::InnerClass::MyEnum" cannot be assigned to a variable of type "enum_local_var_assign_outer_with_wrong_enum_type.gd::MyEnum". +Cannot assign a value of type "enum_local_var_assign_outer_with_wrong_enum_type.gd::InnerClass::MyEnum" as "enum_local_var_assign_outer_with_wrong_enum_type.gd::MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out index 2adcbd9edfa..af3dde663f9 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Value of type "enum_local_var_assign_with_wrong_enum_type.gd::MyOtherEnum" cannot be assigned to a variable of type "enum_local_var_assign_with_wrong_enum_type.gd::MyEnum". +Cannot assign a value of type "enum_local_var_assign_with_wrong_enum_type.gd::MyOtherEnum" as "enum_local_var_assign_with_wrong_enum_type.gd::MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out index 331113dd30d..781b0bc85f4 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot assign a value of type enum_local_var_init_with_wrong_enum_type.gd::MyOtherEnum to variable "local_var" with specified type enum_local_var_init_with_wrong_enum_type.gd::MyEnum. +Cannot assign a value of type "enum_local_var_init_with_wrong_enum_type.gd::MyOtherEnum" as "enum_local_var_init_with_wrong_enum_type.gd::MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out index 6298c026b46..e8c7f86c4f6 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Value of type "enum_value_from_parent.gd::" cannot be assigned to a variable of type "enum_preload_unnamed_assign_to_named.gd::MyEnum". +Cannot assign a value of type "enum_value_from_parent.gd::" as "enum_preload_unnamed_assign_to_named.gd::MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out index b70121ed81c..fb18c94d0b0 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot assign a value of type enum_unnamed_assign_to_named.gd:: to variable "local_var" with specified type enum_unnamed_assign_to_named.gd::MyEnum. +Cannot assign a value of type "enum_unnamed_assign_to_named.gd::" as "enum_unnamed_assign_to_named.gd::MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/lambda_wrong_return.out b/modules/gdscript/tests/scripts/analyzer/errors/lambda_wrong_return.out index 53e2b012e63..69af0984f7d 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/lambda_wrong_return.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/lambda_wrong_return.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot return value of type "String" because the function return type is "int". +Cannot return a value of type "String" as "int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out b/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out index 5e3c446bf6a..08a973503fc 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Value of type "enum_from_outer.gd::Named" cannot be assigned to a variable of type "preload_enum_error.gd::LocalNamed". +Cannot assign a value of type "enum_from_outer.gd::Named" as "preload_enum_error.gd::LocalNamed". diff --git a/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd new file mode 100644 index 00000000000..efd8ad6edb3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd @@ -0,0 +1,16 @@ +const const_color: Color = 'red' + +func func_color(arg_color: Color = 'blue') -> bool: + return arg_color == Color.BLUE + +@warning_ignore("assert_always_true") +func test(): + assert(const_color == Color.RED) + + assert(func_color() == true) + assert(func_color('blue') == true) + + var var_color: Color = 'green' + assert(var_color == Color.GREEN) + + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.out b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.out new file mode 100644 index 00000000000..1b47ed10dc0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd new file mode 100644 index 00000000000..bed9dd0e96c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd @@ -0,0 +1,24 @@ +const const_float_int: float = 19 +const const_float_plus: float = 12 + 22 +const const_float_cast: float = 76 as float + +const const_packed_empty: PackedFloat64Array = [] +const const_packed_ints: PackedFloat64Array = [52] + +@warning_ignore("assert_always_true") +func test(): + assert(typeof(const_float_int) == TYPE_FLOAT) + assert(str(const_float_int) == '19') + assert(typeof(const_float_plus) == TYPE_FLOAT) + assert(str(const_float_plus) == '34') + assert(typeof(const_float_cast) == TYPE_FLOAT) + assert(str(const_float_cast) == '76') + + assert(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY) + assert(str(const_packed_empty) == '[]') + assert(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY) + assert(str(const_packed_ints) == '[52]') + assert(typeof(const_packed_ints[0]) == TYPE_FLOAT) + assert(str(const_packed_ints[0]) == '52') + + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.out b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.out new file mode 100644 index 00000000000..1b47ed10dc0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.gd b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.gd similarity index 100% rename from modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.gd rename to modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.gd diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out new file mode 100644 index 00000000000..6e086a0918b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out @@ -0,0 +1,6 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> INT_AS_ENUM_WITHOUT_MATCH +>> Cannot cast 2 as Enum "cast_enum_bad_enum.gd::MyEnum": no enum member has matching value. +2 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.gd b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.gd similarity index 100% rename from modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.gd rename to modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.gd diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out new file mode 100644 index 00000000000..c19d57f98e4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out @@ -0,0 +1,6 @@ +GDTEST_OK +>> WARNING +>> Line: 4 +>> INT_AS_ENUM_WITHOUT_MATCH +>> Cannot cast 2 as Enum "cast_enum_bad_int.gd::MyEnum": no enum member has matching value. +2 diff --git a/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out b/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out index eef13bbff8d..b8e243769fb 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out +++ b/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out @@ -1,19 +1,19 @@ GDTEST_OK >> WARNING >> Line: 5 ->> INT_ASSIGNED_TO_ENUM +>> INT_AS_ENUM_WITHOUT_CAST >> Integer used when an enum value is expected. If this is intended cast the integer to the enum type. >> WARNING >> Line: 9 ->> INT_ASSIGNED_TO_ENUM +>> INT_AS_ENUM_WITHOUT_CAST >> Integer used when an enum value is expected. If this is intended cast the integer to the enum type. >> WARNING >> Line: 12 ->> INT_ASSIGNED_TO_ENUM +>> INT_AS_ENUM_WITHOUT_CAST >> Integer used when an enum value is expected. If this is intended cast the integer to the enum type. >> WARNING >> Line: 14 ->> INT_ASSIGNED_TO_ENUM +>> INT_AS_ENUM_WITHOUT_CAST >> Integer used when an enum value is expected. If this is intended cast the integer to the enum type. 0 1