Both sides previous revisionPrevious revisionNext revision | Previous revision |
rfc:scalar_type_hinting_with_cast [2014/07/20 12:55] – reöpening ajf | rfc:scalar_type_hinting_with_cast [2021/09/09 06:07] (current) – Fixed a typo in the First Published at URL heiglandreas |
---|
====== Request for Comments: Scalar Type Hinting With Casts ====== | ====== Request for Comments: Scalar Type Hinting With Casts ====== |
* Version: 0.1.6 | * Version: 0.1.9.1 |
* Date: 2012-07-03 (reöpened 2014-07-13, latest update 2014-07-14) | * Date: 2012-07-03 (reopened 2014-07-13, latest update 2014-09-14, withdrawn 2014-09-15) |
* Author: Anthony Ferrara <ircmaxell@php.net> (original) | * Author: Anthony Ferrara <ircmaxell@php.net> (original) |
* Contributors: Andrea Faulds <ajf@ajf.me> (current maintainer) | * Contributors: Andrea Faulds <ajf@ajf.me> (current maintainer) |
* Status: Under Discussion (previously Withdrawn) | * Status: Withdrawn (previously Withdrawn then reopened) |
* First Published at: http://wiki.php.net/rfc/scalar_type_hinting_with_casts | * First Published at: http://wiki.php.net/rfc/scalar_type_hinting_with_cast |
| |
===== Introduction ===== | ===== Introduction ===== |
This RFC discusses a method of adding scalar type hints to PHP while attempting to embrace the dynamic nature of PHP variables. This means that passing a type that does not exactly match the hinted type will cause a cast to happen. This cast will only succeed if the argument can be cleanly converted to the requested type. If it cannot be converted without significant data-loss, an //E_RECOVERABLE_ERROR// will be raised. | This RFC discusses a method of adding scalar type hints to PHP while attempting to embrace the dynamic nature of PHP variables. This means that passing a type that does not exactly match the hinted type will cause a cast to happen. This cast will only succeed if the argument can be cleanly converted to the requested type. If it cannot be converted without significant data-loss, an //E_RECOVERABLE_ERROR// will be raised. |
| |
For consistency, this patch attempts to largely follow //zend_parse_parameters()// for the validation rules, but disallows lossy conversion from float to int (1.5 -> int generates an error) and non-well-formed numeric values for float or int ('123abc' is an error). Since v0.1.6, booleans are handled strictly, also a departure from zpp. | For consistency, this patch attempts to largely follow //zend_parse_parameters()// for the validation rules, but disallows lossy conversion from float to int (1.5 -> int generates an error) and non-well-formed numeric values for float or int ('123abc' is an error). Since v0.1.6, booleans are handled strictly, and since v0.1.9, booleans are not accepted for int, float, numeric and string, also departures from zpp |
| |
=== Rationale for this proposal compared to others === | === Rationale for this proposal compared to others === |
==== Engine Changes ==== | ==== Engine Changes ==== |
| |
The current implementation introduces four new reserved words: //int//, //float//, //bool// and //string//. These were not previously reserved, because casting is a special case in the lexer. | The current implementation introduces five new reserved words: //int//, //float//, //bool//, //string// and //numeric//. These were not previously reserved, because casting is a special case in the lexer. |
| |
If this causes a problem, it would be possible to revert to the previous implementation, where the parser still detects the type hints as object type hints and the compiler (zend_compile.c) then detects the exact value for the type hint, changing the stored hint from IS_OBJECT to the proper type (freeing the string). | If this causes a problem, it would be possible to revert to the previous implementation, where the parser still detects the type hints as object type hints and the compiler (zend_compile.c) then detects the exact value for the type hint, changing the stored hint from IS_OBJECT to the proper type (freeing the string). |
==== Syntax ==== | ==== Syntax ==== |
| |
Four new type hints are introduced with this patch: | Five new type hints are introduced with this patch: |
| |
* //int// - Matching integers only | * //int// - Matching integers only |
* //float// - Matching floating point numbers | * //float// - Matching floating point numbers |
| * //numeric// - Matching integers and floating point numbers (to allow polymorphic functions dealing with numbers) |
* //bool// - Matching boolean parameters only | * //bool// - Matching boolean parameters only |
* //string// - Matching strings only | * //string// - Matching strings only |
==== Conversion Rules ==== | ==== Conversion Rules ==== |
| |
Conversion is allowed only if data-loss does not happen. There are a few exceptions (objects using <nowiki>__toString</nowiki>, strings containing leading numerics, etc). Here's a table of examples. | Conversion is allowed only if data-loss does not happen. There are a few exceptions (objects using <nowiki>__toString</nowiki>, etc.). Here's a table of examples. |
| |
* //fail// indicates an E_RECOVERABLE_ERROR | * //fail// indicates an E_RECOVERABLE_ERROR |
* //notice// indicates an E_NOTICE and a conversion | * //notice// indicates an E_NOTICE and a conversion |
| |
^ value ^ string ^ float ^ int ^ boolean‡^ array ^ | ^ value ^ string ^ float ^ int ^ numeric ^ boolean‡^ array ^ |
^ true (boolean) | pass | pass | pass | pass | fail | | ^ true (boolean) | fail | fail | fail | fail | pass | fail | |
^ false (boolean) | pass | pass | pass | pass | fail | | ^ false (boolean) | fail | fail | fail | fail | pass | fail | |
^ 0 (integer) | pass | pass | pass | fail | fail | | ^ NULL (NULL) | fail | fail | fail | fail | fail | fail | |
^ 1 (integer) | pass | pass | pass | fail | fail | | ^ 0 (integer) | pass | pass | pass | pass | fail | fail | |
^ 12 (integer) | pass | pass | pass | fail | fail | | ^ 1 (integer) | pass | pass | pass | pass | fail | fail | |
^ 12 (double) | pass | pass | pass | fail | fail | | ^ 12 (integer) | pass | pass | pass | pass | fail | fail | |
^ 12.34 (double) | pass | pass | fail | fail | fail | | ^ 12 (double) | pass | pass | pass | pass | fail | fail | |
^ 'true' (string) | pass | fail | fail | fail | fail | | ^ 12.34 (double) | pass | pass | fail | pass | fail | fail | |
^ 'false' (string) | pass | fail | fail | fail | fail | | ^ 'true' (string) | pass | fail | fail | fail | fail | fail | |
^ '0' (string) | pass | pass | pass | fail | fail | | ^ 'false' (string) | pass | fail | fail | fail | fail | fail | |
^ '1' (string) | pass | pass | pass | fail | fail | | ^ '0' (string) | pass | pass | pass | pass | fail | fail | |
^ '12' (string) | pass | pass | pass | fail | fail | | ^ '1' (string) | pass | pass | pass | pass | fail | fail | |
^ '12abc' (string) | pass | fail | fail | fail | fail | | ^ '12' (string) | pass | pass | pass | pass | fail | fail | |
^ '12.0' (string) | pass | pass | pass | fail | fail | | ^ '12abc' (string) | pass | fail | fail | fail | fail | fail | |
^ '12.34' (string) | pass | pass | fail | fail | fail | | ^ '12.0' (string) | pass | pass | pass | pass | fail | fail | |
^ 'foo' (string) | pass | fail | fail | fail | fail | | ^ '12.34' (string) | pass | pass | fail | pass | fail | fail | |
^ array () (array) | fail | fail | fail | fail | pass | | ^ 'foo' (string) | pass | fail | fail | fail | fail | fail | |
^ array (0 => 12) (array) | fail | fail | fail | fail | pass | | ^ array () (array) | fail | fail | fail | fail | fail | pass | |
^ NULL (NULL) | pass | pass | pass | fail | fail | | ^ array (0 => 12) (array) | fail | fail | fail | fail | fail | pass | |
^ %%''%% (string) | pass | fail | fail | fail | fail | | ^ %%''%% (string) | pass | fail | fail | fail | fail | fail | |
^ 1 (resource) | fail | fail | fail | fail | fail | | ^ 1 (resource) | fail | fail | fail | fail | fail | fail | |
^ StdClass | fail | fail* | fail* | fail† | fail | | ^ StdClass | fail | fail* | fail* | fail* | fail† | fail | |
^ implementing __toString | pass | fail* | fail* | fail† | fail | | ^ implementing __toString | pass | fail* | fail* | fail* | fail† | fail | |
| |
<nowiki>*</nowiki>actually //notice// in patch as it stands due to behaviour of default object casting handler | <nowiki>*</nowiki>actually //notice// in patch as it stands due to behaviour of default object casting handler |
==== Errors ==== | ==== Errors ==== |
| |
If a provided hint does not match at all ("foo" passed to an //int// hint), an //E_RECOVERABLE_ERROR// is raised. This includes non-well-formed numerics passed to an //int// or //float// hinted parameter, unlike zend_parse_parameters which would simply raise an //E_NOTICE//. | If a provided hint does not match at all ("foo" passed to an //int// hint), an //E_RECOVERABLE_ERROR// is raised. This includes non-well-formed numerics passed to an //int//, //float// or //numeric// hinted parameter, unlike zend_parse_parameters which would simply raise an //E_NOTICE//. |
| |
==== Defaults ==== | ==== Defaults ==== |
=== NULL defaults (nullable hints) === | === NULL defaults (nullable hints) === |
| |
Normally, scalar typehints are non-nullable and NULL is casted like any other value. However, if the default value for a scalar typehint is NULL, the typehint is considered nullable. This means that while values of other types would be casted, NULL will not, e.g.: | The scalar types can be nullable just like any other type. If a parameter does not have a default value of NULL, then NULL is not a permitted value. If it does have a default value of NULL, and is therefore nullable, then the value NULL is accepted and will not be casted. |
| |
<code php> | |
function foo(int $a) { var_dump($a); } | |
function bar(int $a = NULL) { var_dump($a); } | |
| |
foo(NULL); // int(0) | |
bar(NULL); // NULL | |
</code> | |
| |
==== References ==== | ==== References ==== |
* //int convert_to_{type}_safe_ex(zval <nowiki>**</nowiki>ptr)// - Separate zval if not a reference, and convert to {type}. Return indicates clean conversion (FAILURE indicates unclean conversion). | * //int convert_to_{type}_safe_ex(zval <nowiki>**</nowiki>ptr)// - Separate zval if not a reference, and convert to {type}. Return indicates clean conversion (FAILURE indicates unclean conversion). |
| |
These functions pairs exist for //long//, //double//, //string// and //boolean//. | These functions pairs exist for //long//, //double//, //string//, //boolean// and //numeric//. |
| |
==== New Methods ==== | ==== New Methods ==== |
* //isBool()// - boolean to determine if parameter is type-hinted as a boolean. | * //isBool()// - boolean to determine if parameter is type-hinted as a boolean. |
* //isString()// - boolean to determine if parameter is type-hinted as a string. | * //isString()// - boolean to determine if parameter is type-hinted as a string. |
| * //isNumeric()// - boolean to determine if parameter is type-hinted as numeric. |
| |
==== Patch ==== | ==== Patch ==== |
| |
The modifications necessary to implement this feature exist on the [[https://github.com/TazeTSchnitzel/php-src/tree/scalar_type_hints|scalar_type_hints branch of Andrea's GitHub fork]] (forked from the [[https://github.com/ircmaxell/php-src/tree/scalar_type_hints|branch on ircmaxell's GitHub fork]]). It is still a work-in-progress, and should be considered unstable at this time. | The modifications necessary to implement this feature exist on the [[https://github.com/TazeTSchnitzel/php-src/tree/scalar_type_hints|scalar_type_hints branch of Andrea's GitHub fork]] (forked from the [[https://github.com/ircmaxell/php-src/tree/scalar_type_hints|branch on ircmaxell's GitHub fork]]). It is stable to the best of Andrea's knowledge, with its tests passing and it breaking no known tests on her machine nor Travis. |
| |
===== Possible Changes ===== | ===== Possible Changes ===== |
| |
| For points I'm unsure on, this section lists possible future changes to the RFC. |
| |
==== Float to Int Casting Rules ==== | ==== Float to Int Casting Rules ==== |
| |
Both the author of this RFC (Anthony) and the current maintainer (Andrea) are yet to settle on one specific option. | Both the author of this RFC (Anthony) and the current maintainer (Andrea) are yet to settle on one specific option. |
==== Handling of "123abc" for int and float ==== | ==== Handling of "123abc" for int, float and numeric ==== |
| |
This has been changed to E_RECOVERABLE_ERROR, but should it perhaps be something softer, like E_NOTICE or E_WARNING? | This has been changed to E_RECOVERABLE_ERROR, but should it perhaps be something softer, like E_NOTICE or E_WARNING? |
foo("1a"); // E_RECOVERABLE_ERROR | foo("1a"); // E_RECOVERABLE_ERROR |
foo("a"); // E_RECOVERABLE_ERROR | foo("a"); // E_RECOVERABLE_ERROR |
| foo(""); // E_RECOVERABLE_ERROR |
foo(999999999999999999999999999999999999); // E_RECOVERABLE_ERROR (since it's not exactly representable by an int) | foo(999999999999999999999999999999999999); // E_RECOVERABLE_ERROR (since it's not exactly representable by an int) |
| foo('999999999999999999999999999999999999'); // E_RECOVERABLE_ERROR (since it's not exactly representable by an int) |
foo(1.5); // E_RECOVERABLE_ERROR | foo(1.5); // E_RECOVERABLE_ERROR |
foo(array()); // E_RECOVERABLE_ERROR | foo(array()); // E_RECOVERABLE_ERROR |
foo("1a"); // E_RECOVERABLE_ERROR | foo("1a"); // E_RECOVERABLE_ERROR |
foo("a"); // E_RECOVERABLE_ERROR | foo("a"); // E_RECOVERABLE_ERROR |
| foo(""); // E_RECOVERABLE_ERROR |
| foo(1.5); // float(1.5) |
| foo(array()); // E_RECOVERABLE_ERROR |
| foo(new StdClass); // E_RECOVERABLE_ERROR |
| ?> |
| </file> |
| |
| ==== Numeric Hints ==== |
| |
| <file php numeric_hint.php> |
| <?php |
| function foo(numeric $a) { |
| var_dump($a); |
| } |
| foo(1); // int(1) |
| foo("1"); // int(1) |
| foo(1.0); // float(1) |
| foo("1a"); // E_RECOVERABLE_ERROR |
| foo("a"); // E_RECOVERABLE_ERROR |
| foo(""); // E_RECOVERABLE_ERROR |
foo(1.5); // float(1.5) | foo(1.5); // float(1.5) |
foo(array()); // E_RECOVERABLE_ERROR | foo(array()); // E_RECOVERABLE_ERROR |
foo("1a"); // string "1a" | foo("1a"); // string "1a" |
foo("a"); // string "a" | foo("a"); // string "a" |
| foo(""); // string "" |
foo(1.5); // string "1.5" | foo(1.5); // string "1.5" |
foo(array()); // E_RECOVERABLE_ERROR | foo(array()); // E_RECOVERABLE_ERROR |
foo("1a"); // E_RECOVERABLE_ERROR | foo("1a"); // E_RECOVERABLE_ERROR |
foo("a"); // E_RECOVERABLE_ERROR | foo("a"); // E_RECOVERABLE_ERROR |
| foo(""); // E_RECOVERABLE_ERROR |
foo(1.5); // E_RECOVERABLE_ERROR | foo(1.5); // E_RECOVERABLE_ERROR |
foo(array()); // E_RECOVERABLE_ERROR | foo(array()); // E_RECOVERABLE_ERROR |
?> | ?> |
</file> | </file> |
| |
| ===== Proposed Voting Choices ===== |
| |
| As this is a language change, a 2/3 majority is required. Voting started 2014-09-14 and ends 2014-09-21. |
| |
| It will be a straight Yes/No vote. |
| |
===== More Information ===== | ===== More Information ===== |
* 0.1.5 - Note on NULL default values | * 0.1.5 - Note on NULL default values |
* 0.1.6 - Booleans are now strict | * 0.1.6 - Booleans are now strict |
| * 0.1.7 - Types are now not nullable by default |
| * 0.1.8 - Added numeric typehint |
| * 0.1.8.1 - Overflow prevention for int hints |
| * 0.1.9 - Booleans not accepted for int, float, numeric or string |
| * 0.1.9.1 - Added "" to tests, patch is stable |