Print a GDScript warning when comparing floating-point numbers directly

`is_equal_approx()` or `is_zero_approx()` should be used instead
to ensure consistent results.
This commit is contained in:
Hugo Locurcio 2021-11-17 20:10:48 +01:00
parent a4f2ea91a1
commit e6254946d4
No known key found for this signature in database
GPG key ID: 39E8F8BE30B0A49C
4 changed files with 22 additions and 0 deletions

View file

@ -495,6 +495,9 @@
<member name="debug/gdscript/warnings/exclude_addons" type="bool" setter="" getter="" default="true">
If [code]true[/code], scripts in the [code]res://addons[/code] folder will not generate warnings.
</member>
<member name="debug/gdscript/warnings/float_comparison" type="int" setter="" getter="" default="1">
If [code]true[/code], enables warnings when comparing a floating-point value with another floating-point value or an integer value. Due to the limitations of floating-point math, such comparisons may not always evaluate to [code]true[/code] as you expect. Instead, [method @GlobalScope.is_equal_approx] or [method @GlobalScope.is_zero_approx] should be used to perform reliable floating-point number comparisons.
</member>
<member name="debug/gdscript/warnings/function_used_as_property" type="int" setter="" getter="" default="1" deprecated="This warning is never produced. When a function is used as a property, a [Callable] is returned.">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when using a function as if it is a property.
</member>

View file

@ -2865,6 +2865,20 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
if (p_binary_op->variant_op == Variant::OP_DIVIDE && left_type.builtin_type == Variant::INT && right_type.builtin_type == Variant::INT) {
parser->push_warning(p_binary_op, GDScriptWarning::INTEGER_DIVISION);
}
const bool number_to_float_direct_comparison =
(p_binary_op->variant_op == Variant::OP_EQUAL || p_binary_op->variant_op == Variant::OP_NOT_EQUAL) &&
(left_type.builtin_type == Variant::INT || left_type.builtin_type == Variant::FLOAT) &&
right_type.builtin_type == Variant::FLOAT;
const bool float_to_number_direct_comparison =
(p_binary_op->variant_op == Variant::OP_EQUAL || p_binary_op->variant_op == Variant::OP_NOT_EQUAL) &&
left_type.builtin_type == Variant::FLOAT &&
(right_type.builtin_type == Variant::INT || right_type.builtin_type == Variant::FLOAT);
if (number_to_float_direct_comparison || float_to_number_direct_comparison) {
// GDScript allows comparing floats with integers with implicit conversion, but the precision issue remains nonetheless.
parser->push_warning(p_binary_op, GDScriptWarning::FLOAT_COMPARISON);
}
#endif
if (p_binary_op->left_operand->is_constant && p_binary_op->right_operand->is_constant) {

View file

@ -119,6 +119,8 @@ String GDScriptWarning::get_message() const {
return "Assert statement will raise an error because the expression is always false.";
case INTEGER_DIVISION:
return "Integer division, decimal part will be discarded.";
case FLOAT_COMPARISON:
return "Direct floating-point comparison (this may not evaluate to `true` as you expect). Instead, use `is_equal_approx()` or `is_zero_approx()` for an approximate but predictable comparison.";
case NARROWING_CONVERSION:
return "Narrowing conversion (float is converted to int and loses precision).";
case INT_AS_ENUM_WITHOUT_CAST:
@ -221,6 +223,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"ASSERT_ALWAYS_TRUE",
"ASSERT_ALWAYS_FALSE",
"INTEGER_DIVISION",
"FLOAT_COMPARISON",
"NARROWING_CONVERSION",
"INT_AS_ENUM_WITHOUT_CAST",
"INT_AS_ENUM_WITHOUT_MATCH",

View file

@ -75,6 +75,7 @@ public:
ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true.
ASSERT_ALWAYS_FALSE, // Expression for assert argument is always false.
INTEGER_DIVISION, // Integer divide by integer, decimal part is discarded.
FLOAT_COMPARISON, // Floating-point number is compared with another number using `==` or `!=`, leading to inconsistent results.
NARROWING_CONVERSION, // Float value into an integer slot, precision is lost.
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.
@ -127,6 +128,7 @@ public:
WARN, // ASSERT_ALWAYS_TRUE
WARN, // ASSERT_ALWAYS_FALSE
WARN, // INTEGER_DIVISION
WARN, // FLOAT_COMPARISON
WARN, // NARROWING_CONVERSION
WARN, // INT_AS_ENUM_WITHOUT_CAST
WARN, // INT_AS_ENUM_WITHOUT_MATCH