rfc:stricter_implicit_boolean_coercions

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rfc:stricter_implicit_boolean_coercions [2022/05/29 08:56] iquitorfc:stricter_implicit_boolean_coercions [2022/06/20 16:00] (current) – Change status to declined and close voting iquito
Line 1: Line 1:
 ====== PHP RFC: Stricter implicit boolean coercions ====== ====== PHP RFC: Stricter implicit boolean coercions ======
-  * Version: 1.7+  * Version: 1.9
   * Date: 2022-05-16   * Date: 2022-05-16
   * Author: Andreas Leathley, <a.leathley@gmx.net>   * Author: Andreas Leathley, <a.leathley@gmx.net>
-  * Status: Under Discussion+  * Status: Declined
   * First Published at: http://wiki.php.net/rfc/stricter_implicit_boolean_coercions   * First Published at: http://wiki.php.net/rfc/stricter_implicit_boolean_coercions
  
Line 10: Line 10:
 When not using strict_types in PHP, scalar type coercions have become less lossy/surprising in the last years - non-number-strings cannot be passed to an int type (leads to a TypeError), floats (or float-strings) with a fractional part cannot be passed to an int type (leads to a deprecation notice since 8.1 because it loses information). The big exception so far are booleans: you can give a typed boolean any scalar value and it will convert any non-zero (and non-empty-string) value to true without any notice. When not using strict_types in PHP, scalar type coercions have become less lossy/surprising in the last years - non-number-strings cannot be passed to an int type (leads to a TypeError), floats (or float-strings) with a fractional part cannot be passed to an int type (leads to a deprecation notice since 8.1 because it loses information). The big exception so far are booleans: you can give a typed boolean any scalar value and it will convert any non-zero (and non-empty-string) value to true without any notice.
  
-Some examples where this might lead to unexpected outcomes:+Some examples how this might lead to surprising behavior and loss of information:
  
 <PHP> <PHP>
-function toBool(bool $a) +function toBool(bool $a) { var_dump($a); }
-{ +
-  var_dump($a); +
-}+
  
 toBool('0'); // bool(false) toBool('0'); // bool(false)
Line 67: Line 64:
 This RFC boils down to these questions: This RFC boils down to these questions:
  
-  * Are you losing information when you reduce a value like -375, "false" or NaN to true for a typed boolean? +  * Are you losing information when you reduce a value like -375, "false" or 0.01 to true for a typed boolean? 
-  * Would you want to know when a value like -375, "false" or NaN is given to a typed boolean in a codebase?+  * Would you want to know when a value like -375, "false" or 0.01 is given to a typed boolean in a codebase?
   * How likely is it that such a coercion is unintended?   * How likely is it that such a coercion is unintended?
-  * What about other boolean coercions in PHP? (this is handled in the next section)+  * What about other boolean coercions in PHP? (handled in the [[stricter_implicit_boolean_coercions#other_boolean_coercions_in_php|next section]]) 
 +  * How does this RFC fit in with the existing scalar type coercions? (handled in the [[stricter_implicit_boolean_coercions#overview_of_scalar_type_coercions|Overview of scalar type coercions]] section)
  
 The main motivation for this RFC is to reduce the possibility of errors when using the boolean type in a similar way that you cannot give a typed int a non-number string - if you provide "false" or "49x" to an int argument it will result in a TypeError. It will not be silently coerced to 0 (or 49), as that loses information and can lead to subtle bugs. This RFC does the same thing for boolean types: The main motivation for this RFC is to reduce the possibility of errors when using the boolean type in a similar way that you cannot give a typed int a non-number string - if you provide "false" or "49x" to an int argument it will result in a TypeError. It will not be silently coerced to 0 (or 49), as that loses information and can lead to subtle bugs. This RFC does the same thing for boolean types:
Line 91: Line 89:
 Typed booleans (arguments, returns, properties) as discussed in this RFC are not the only part of PHP where implicit boolean coercions happen. They also occur in expressions like ''if'', the ternary operator ''?:'', or logical operators ''&&'' / ''||''. Whenever an expression in that context is not clearly true or false it is implicitly coerced to true or false. Typed booleans (arguments, returns, properties) as discussed in this RFC are not the only part of PHP where implicit boolean coercions happen. They also occur in expressions like ''if'', the ternary operator ''?:'', or logical operators ''&&'' / ''||''. Whenever an expression in that context is not clearly true or false it is implicitly coerced to true or false.
  
-However in these expressions you can use any values and are not restricted to scalar types like with typed booleans:+Using strict_types is an established way to change how scalar type coercions work (by prohibiting any coercions) but it does not affect implicit boolean coercions in expressions. But even in coercive mode there is a big difference between boolean expressions and boolean type coercions:
  
 <PHP> <PHP>
Line 112: Line 110:
 </PHP> </PHP>
  
-Typed booleans behave differently compared to these expressions because they do not accept arrays, resources, objects and null. Further restricting typed booleans is therefore not a change which makes the language more inconsistent, on the contrary, it could be an opportunity to differentiate these two use cases from each other, as they often have different expectations already:+Typed booleans behave differently compared to boolean expressions because they do not accept arrays, resources, objects and null. Further restricting typed booleans is therefore not a change which makes the language more inconsistent, on the contrary, it could be an opportunity to differentiate these two use cases more clearly from each other, as they often have different expectations already:
  
 <PHP> <PHP>
Line 154: Line 152:
 ===== Overview of scalar type coercions ===== ===== Overview of scalar type coercions =====
  
-This is the status of coercions if this RFC passes (and any deprecation notices and TypeErrors are avoided) - the new behavior can be seen in the last column (To bool):+This is the status of coercions if this RFC passes (and any deprecation notices and TypeErrors are avoided) - the new behavior can be seen in the last column (To bool), it is quite symmetrical to the existing behavior in the "From bool" row:
  
 |              ^ To string                              ^ To int                                                   ^ To float                                    ^ To bool                           ^ |              ^ To string                              ^ To int                                                   ^ To float                                    ^ To bool                           ^
-^ From string  |                                        | only possible for numeric values with no fractional part | only possible for numeric values            | only possible for "", "0" and "1"+^ From string  |                                        | only allowed for numeric values with no fractional part  | only allowed for numeric values             | only allowed for "", "0" and "1"
-^ From int     | always possible                        |                                                          | always possible                             | only possible for 0 and 1         | +^ From int     | always possible                        |                                                          | always possible                             | only allowed for 0 and 1         | 
-^ From float   | always possible                        | only possible if there is no fractional part             |                                             | only possible for 0 and 1         | +^ From float   | always possible                        | only allowed if there is no fractional part              |                                             | only allowed for 0 and 1         | 
-^ From bool    | always possible (coerced to "" or "1") | always possible (coerced to 0 or 1)                      | always possible (coerced to 0 or 1)                                           |+^ From bool    | always possible (coerced to "" or "1") | always possible (coerced to 0 or 1)                      | always possible (coerced to 0 or 1)                                          |
  
-This RFC would further reduce the gap between strict mode and coercive mode, as even in coercive mode no information would be lost and only values that are reasonable are accepted (or a deprecation notice or TypeError is thrown). All allowed coercions can be reversed to end up with the original value or almost the same ("0" can become "" and vice versa) - that is something this RFC enables, as without this RFC reversing a coercion to boolean will often not lead back to the original value, pointing out the loss of information in that case.+This RFC would further reduce the gap between strict mode and coercive mode, as even in coercive mode no information would be lost when coercing a scalar value and only values that are reasonable are accepted (otherwise a deprecation notice is emitted). All allowed coercions can be reversed to end up with the original value or almost the same ("0" can become "") - that is something this RFC makes possible, as without this RFC reversing a coercion to boolean will often not lead back to the original value. These examples illustrate reversibility and the loss of information: 
 + 
 +<PHP> 
 +function toBool(bool $a) { return $a; } 
 +function toString(string $a) { return $a; } 
 +function toInt(int $a) { return $a; } 
 +function toFloat(float $a) { return $a; } 
 + 
 +toString(toBool('')); // '' is coerced to false and then back to '' 
 +toInt(toBool(0)); // 0 coerced to false and then back to 0 
 +toFloat(toBool(0.0)); // 0.0 coerced to false and then back to 0.0 
 + 
 +toString(toBool('success'));  
 +// => 'success' is coerced to true and then back to '1' 
 +// the new deprecation notice of this RFC points out the loss of information 
 +                              
 +toInt(toBool(-33)); 
 +// => -33 is coerced to true and then back to 1 
 +// the new deprecation notice of this RFC points out the loss of information 
 + 
 +toFloat(toBool(0.01));  
 +// => 0.01 is coerced to true and then back to 1 
 +// the new deprecation notice of this RFC points out the loss of information 
 +                       
 +// Existing behavior leading to TypeErrors and deprecation notices: 
 +toFloat('success'); // TypeError, not a numeric string 
 +toInt('1.6'); // Deprecation notice because fractional part is lost 
 +toString(['']); // TypeError, array cannot be implicitly coerced to string 
 +toBool(null); // TypeError, null cannot be implicitly coerced to bool 
 +</PHP> 
 + 
 +Having as little information loss as possible when coercing scalar types makes them safer to use and more predictable.
  
 ===== Implementation notes ===== ===== Implementation notes =====
Line 194: Line 223:
 </PHP> </PHP>
  
-With the many deprecation notices that appeared in PHP 8.0 and 8.1 there is some wariness if more deprecation notices are worth it. These are the arguments why the RFC author thinks it will not cause too much pain:+With the many deprecation notices that appeared in PHP 8.0 and 8.1 there is some wariness if new deprecation notices are worth it. These are the arguments why the RFC author thinks it will not cause too much pain:
  
   * Each individual case is easy to fix, the easiest (but also least useful) is to loosly compare a value to true ($value == true) instead of directly giving the value to a typed bool   * Each individual case is easy to fix, the easiest (but also least useful) is to loosly compare a value to true ($value == true) instead of directly giving the value to a typed bool
Line 203: Line 232:
 ===== Future Scope ===== ===== Future Scope =====
  
-While this RFC only targets boolean coercions when not using strict_types, the overall goal is to have a solid and easy-to-understand foundation of type coercions between scalar values, and coercions to booleans are the biggest missing piece when looking at that.+While this RFC only targets boolean coercions when not using strict_types, this is just the last missing piece for the overall goal of having a solid and easy-to-understand foundation of type coercions between scalar values.
  
-One benefit of these well-developed coercions could be to make them available in an explicit way to PHP developers. Having functions like ''is_coerceable_to_bool'' and ''coerce_to_bool'' (and with similar functions for int and float) that behave exactly as giving a value to a boolean argument could be useful when receiving input from a form or database. Compared to the current explicit type coercions (''(bool)'', ''boolval'', ''(int)'' or ''(float)'') this would allow only a certain subset of values instead of coercing any value, giving developers an effective way to make sure they are dealing with values that make sense - or fail early if an unexpected value is encountered. And because it is based on the type coercion behavior of PHP the learning curve would be low.+One benefit of these well-developed coercions could be to make them available in an explicit way to PHP developers. Having functions like ''is_coerceable_to_bool'' and ''coerce_to_bool'' (and with similar functions for intfloat and string) that behave exactly as giving a value to a boolean argument could be useful when receiving input from a form or database. Compared to the current explicit type coercions (''(bool)'', ''boolval'', ''(int)'' or ''(float)'') this would allow only a certain subset of values instead of coercing any value, giving developers an effective way to make sure they are dealing with values that make sense - or fail early if an unexpected value is encountered. And because it is based on the type coercion behavior of PHP the learning curve would be low and the knowledge would be universally useful within the language.
  
-If this RFC is accepted I would likely follow up with a discussion about a way to invoke the implicit coercion behavior in an explicit way, which would make it available even when using strict_types.+An example of how these functions could look like can be found on Github in [[https://github.com/squirrelphp/scalar-types|squirrelphp/scalar-types]] (written in PHP). This is just a preliminary example that would need to be discussed further with a follow-up RFC.
      
 ===== Proposed PHP Version ===== ===== Proposed PHP Version =====
Line 219: Line 248:
   * Implicit boolean expressions (as used in if, ternary, logic operators) are not affected.   * Implicit boolean expressions (as used in if, ternary, logic operators) are not affected.
   * FILTER_VALIDATE_BOOLEAN in the filter extension is not affected.   * FILTER_VALIDATE_BOOLEAN in the filter extension is not affected.
 +  
 +===== Vote =====
 +
 +Voting started on 2022-06-06 and will end on 2022-06-20.
 +
 +<doodle title="Accept Stricter implicit boolean coercions RFC as proposed?" auth="iquito" voteType="single" closed="true">
 +   * Yes
 +   * No
 +</doodle>
  
 ===== Patches and Tests ===== ===== Patches and Tests =====
rfc/stricter_implicit_boolean_coercions.1653814601.txt.gz · Last modified: 2022/05/29 08:56 by iquito