Table of Contents

PHP RFC: Deprecate implicit non-integer-compatible float to int conversions

Introduction

PHP is a dynamically typed language and as such implicit type coercion naturally arises, most of these are harmless and rather convenient. However, float to int conversions can lead to data loss such as when the float is outside the platform integer range, or it has a fractional part. This extends to the conversion of float strings when converted to int.

Terminology

A float is said to be integer-compatible if posses the following characteristics:

Proposal

Emit an E_DEPRECATED deprecation diagnostic for implicit conversion from float and float strings to int if the floating point number is not integer-compatible.

If the conversion happens from a float the diagnostic message is:

Implicit conversion to int from non-compatible float %f

If the conversion happens from a float-string the diagnostic message is:

Implicit conversion to int from non-compatible float-string %s

Where %f and %s correspond to the value of the float/float-string value which is not integer-compatible.

Raise these deprecation diagnostics to TypeError in the next major version (PHP 9.0).

Rationale

Following the changes in behaviour and definition of numeric strings in PHP 8 1) 2) it should be a reasonable expectation to safely be able to use PHP's type juggling from string to integer as such data can come from a variety of places (HTTP request, database query, text file, etc. ). However, because floats, and by extension float strings, get silently converted to int there is no way to know if the data provided is erroneous and/or provokes data loss.

The lack of possibility of knowing if data loss arises necessitates the use of the strict_type mode, which is an issue in itself when using a function which returns a float but given the input arguments an int compatible return is to be expected, this mostly affects mathematical functions, the most notable example being the round() function when passing a non-positive precision.

The use of a float or float string as a string offset already emits a warning as it needs to perform a conversion, this proposal would generalize this aspect to other areas of PHP.

Finally, attempting to pass a float or float string which exceeds the range representable by an int as an argument to a parameter with an int type declaration already causes a TypeError to be thrown, regardless of typing mode.

Implementation notes

A new C function is_long_compatible() is introduced which performs a round trip (i.e. (double)(zend_long)) check to establish if a float is integer-compatible.

A new zend_dval_to_lval_safe() C function is introduced which performs a check to is_long_compatible() that if it fails will emit the deprecation diagnostic.

The zend_get_long_func() is modified to accept an additional argument named is_lax, which purpose is to toggle between using zend_dval_to_lval() or zend_dval_to_lval_safe(), as this function is used by the (int) cast.

Backward Incompatible Changes

The following operations will now emit an E_DEPRECATED if a non-integer-compatible floats or float string is used:

The following operations will now emit an E_DEPRECATED if a non-integer-compatible float is used:

Proposed PHP Version

Next minor version: PHP 8.1.

RFC Impact

To Existing Extensions

Test output might need to be adjusted as the zval_get_long() (and TBD convert_to_long()) will call the new zend_dval_to_lval_safe() function instead of zend_dval_to_lval()

Extensions which call directly zend_get_long_func() will need to be adjusted to either use zval_get_long() or pass explicitly the new lax flag.

To OPcache

Rules about accepting floats instead of int would need to be reviewed as emitting diagnostics pose an issue from my understanding

Unaffected PHP Functionality

Future Scope

Proposed Voting Choices

As per the voting RFC a yes/no vote with a 2/3 majority is needed for this proposal to be accepted.

Accept Deprecate implicit non-integer-compatible float to int conversions RFC proposal
Real name Yes No
alcaeus (alcaeus)  
asgrim (asgrim)  
ashnazg (ashnazg)  
crell (crell)  
daverandom (daverandom)  
derick (derick)  
galvao (galvao)  
girgias (girgias)  
ilutov (ilutov)  
kalle (kalle)  
kguest (kguest)  
kocsismate (kocsismate)  
lcobucci (lcobucci)  
marandall (marandall)  
mariano (mariano)  
nikic (nikic)  
ocramius (ocramius)  
patrickallaert (patrickallaert)  
pierrick (pierrick)  
pollita (pollita)  
reywob (reywob)  
santiagolizardo (santiagolizardo)  
sergey (sergey)  
svpernova09 (svpernova09)  
tandre (tandre)  
theodorejb (theodorejb)  
thorstenr (thorstenr)  
trowski (trowski)  
twosee (twosee)  
Final result: 29 0
This poll has been closed.

Patches and Tests

Patch: https://github.com/php/php-src/pull/6661

Implementation

Implemented in https://github.com/php/php-src/commit/b6958bb8476306c2f6ce110782330c41e6a5df3a

References

Initial mailing list discussion: <https://externals.io/message/113077>
Revised mailing list discussion: <https://externals.io/message/113371>