rfc:nullable-casting

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
Last revisionBoth sides next revision
rfc:nullable-casting [2018/08/08 20:40] david.prowebrfc:nullable-casting [2019/04/09 05:54] – Add link to Annoucement and discussion thread guilliamxavier
Line 1: Line 1:
 ====== PHP RFC: Nullable Casting ====== ====== PHP RFC: Nullable Casting ======
  
-  * Version: 0.9 +  * Version: 0.2-draft 
-  * Date: 2018-08-07 +  * Date: 2019-03-17 
-  * Author: David Rodriguesdavid.proweb@gmail.com +  * Author: David Rodrigues (david.proweb@gmail.com), Guilliam Xavier (guilliam.xavier@gmail.com) 
-  * Status: Draft +  * Status: Under Discussion 
-  * Discussion at: https://externals.io/message/102997+  * First Published at: http://wiki.php.net/rfc/nullable-casting
  
 ===== Introduction ===== ===== Introduction =====
  
-PHP supports expression casting by using ''(intexpression'', but it doesn'consider that the expression could be a nullable typeas introduced by PHP 7.1. Due to the lack of support for the nullable casting, is necessary to create some additional codes to keep a nullable output as nullable after a casting, for example.+PHP supports expression casting to primitive type (like ''int''by using "''('' //type// '')'' //expression//", but it currently doesn'allow to use a nullable type as introduced by PHP 7.1 (e.g. ''?int''). 
 + 
 +Due to the lack of support for nullable casting, it is necessary to write additional code to preserve possible ''null'' value through the type conversion. This feature would also bring more consistency and completeness to the existing language. 
 + 
 +===== Motivating Example ===== 
 + 
 +In strict type-checking mode (<php><?php declare(strict_types=1);</php>), given two functions like the following (disclaimer: dummy implementation): 
 + 
 +<PHP> 
 +function getInt(): int 
 +
 +    return mt_rand(); 
 +
 + 
 +function processString(string $s): void 
 +
 +    printf("process a string of %d byte(s)\n", strlen($s)); 
 +
 +</PHP> 
 + 
 +then the following call: 
 + 
 +<PHP> 
 +processString(getInt()); 
 +</PHP> 
 + 
 +will throw a ''TypeError'' ("Argument 1 passed to processString() must be of the type string, int given"), but here we can use a cast (''int'' to ''string'' conversion is always safe): 
 + 
 +<PHP> 
 +processString((string) getInt()); 
 +</PHP> 
 + 
 +(which will print something like "process a string of 9 byte(s)"). 
 + 
 +Now given two functions like the following (with //nullable// type declarations): 
 + 
 +<PHP> 
 +function getIntOrNull(): ?int 
 +
 +    $r = mt_rand(); 
 +    return $r % 2 === 0 ? $r : null; 
 +
 + 
 +function processStringOrNull(?string $s): void 
 +
 +    if ($s === null) { 
 +        printf("process null\n"); 
 +    } else { 
 +        printf("process string of %d byte(s)\n", strlen($s)); 
 +    } 
 +
 +</PHP> 
 + 
 +then the following call: 
 + 
 +<PHP> 
 +processStringOrNull(getIntOrNull()); 
 +</PHP> 
 + 
 +will sometimes work (print "process null") and sometimes throw a ''TypeError'' ("Argument 1 passed to processStringOrNull() must be of the type string or null, int given"), so we would want to use a "//nullable// cast": 
 + 
 +<PHP> 
 +processStringOrNull((?string) getIntOrNull()); 
 +</PHP> 
 + 
 +but currently this syntax is not supported ("Parse error: syntax error, unexpected '?'") and we must resort to something more verbose (and error-prone) like: 
 + 
 +<PHP> 
 +processStringOrNull(($tmp = getIntOrNull()) === null ? null : (string) $tmp); 
 +unset($tmp); 
 +</PHP> 
 + 
 +or writing custom casting functions. 
 + 
 +(Note that in //weak// type-checking mode, there is never a ''TypeError'', the ''?int'' is automatically converted to ''?string'', correctly preserving a ''null'' value. But we can prefer strict typing, to catch unintended conversions.) 
 + 
 +==== settype() ==== 
 + 
 +When the desired type is not known before runtime, we cannot use a cast operator, but we can use the ''settype()'' function, for example
 + 
 +<PHP> 
 +function getIntOrNullAs(string $type) 
 +
 +    $x = getIntOrNull(); 
 +    settype($x, $type); 
 +    return $x; 
 +
 +</PHP> 
 + 
 +but currently <php>$type</php> cannot contain a nullable type like <php>"?string"</php> ("Warning: settype(): Invalid type", <php>$x</php> not converted).
  
 ===== Proposal ===== ===== Proposal =====
  
-The proposal is to add support to nullable casting to current casting feature. Basically, ''(int)'' is the default int casting, and ''(?int)'' will be a nullable int casting. Generally speaking, what changes is the possibility of using a ''?'' before the type of casting, turning it into a nullable cast.+The proposal is to add support of nullable types to the current casting feature. Basically, ''(int)'' is the "plain" int cast, and ''(?int)'' will be a nullable int cast. Generally speaking, what changes is the possibility to use leading question mark symbol (''?''before the type of a cast, turning it into a nullable cast.
  
-The difference between casting and nullable casting is that it the expression is null, it should be keeped as nullinstead to be forced to the destination cast.+The only difference of nullable casting compared to plain casting is that if the expression value is ''null'', it will be kept as ''null'' instead of being forced to the destination plain type:
  
-Input                   (cast) - default       (?cast) - nullable     Same result?          +               //type//: int    bool    float  ^  string  ^  array    object  
-| int (123              | int (123)              | int (123)              | Yes                   | +^   ''(''//type//'')''null|       false   0.0     %%""%%   %%[]%%   %%{}%%  
-null                    int (0)                null                   | No                    | +^  ''(?''//type//'')''null null   null    null    null    |  null     null    |
-| bool (true)             | bool (true)            | bool (true)            | Yes                   | +
-| bool (false)            bool (false)           | bool (false)           | Yes                   | +
-| null                    | bool (false)           | null                   | No                    | +
-| float (1.23)            | float (1.23)           | float (1.23)           | Yes                   | +
-| null                    | float (0.0)            null                   | No                    | +
-| string ("test")         string ("test"       string ("test"       | Yes                   +
-| null                    | string (""           | null                   No                    | +
-| array ([1, 2, 3])       | array ([1, 2, 3])      | array ([1, 2, 3])      | Yes                   | +
-| null                    | array ([])             | null                   | No                    | +
-| object ({ a => 1 })     | object ({ a => 1 })    object ({ a => 1 })    Yes                   | +
-null                    | object ({})            | null                   | No                    |+
  
 +**Notes:**
 +  * The ''(unset)'' cast will not be affected (see the "Unaffected PHP Functionality" section).
 +  * The PHP parser is not sensitive to spaces in e.g. "''( int )''" cast and "''? int''" type declaration, so e.g. "''( ? int )''" will be identical to "''(?int)''".
 +  * The PHP parser does not distinguish between ''(integer)'' and ''(int)'' casts, so ''(?integer)'' will be identical to ''(?int)''. Likewise for ''(?boolean)'' vs ''(?bool)'', ''(?double)'' or ''(?real)'' vs ''(?float)'', and ''(?binary)'' vs ''(?string)''.
  
-**Note:** the ''(unset)'' will not be affected, returning ''null'' in any case.+If the expression value is not ''null'', nullable casting will give the same result as plain casting: e.g. (?int)false will give 0, (?array)%%""%% will give %%[""]%%.
  
-==== Example ====+==== Additional proposal for settype() ==== 
 + 
 +Additionally, it was requested on the mailing list to consider adding support of nullable types to the ''settype()'' function, e.g. <php>settype($variable, "?int")</php>, which here would be the same as <php>$variable = (?int)$variable;</php> and return <php>true</php> (but in general <php>"?int"</php> could be a dynamic string). 
 + 
 +In short, for a currently valid <php>$type</php> argument to <php>settype($variable, $type)</php>, it would enable to use <php>'?'.$type</php> to preserve nullability. 
 + 
 +=== "?null" === 
 + 
 +In <php>"?null"</php>, the "''?''" is redundant ("nullable null"), but it could happen in dynamic code, e.g. <php>settype($x, '?' . gettype($y))</php> when <php>$y</php> is <php>null</php>
 + 
 +Possible options: 
 +  - Allow it as equivalent to plain <php>"null"</php> silently. 
 +  - Allow it as equivalent to plain <php>"null"</php> but emit a specific Notice. 
 +  - Disallow it and emit a specific Warning (like the existing "Cannot convert to resource type"). 
 +  - Disallow it and emit the existing generic Warning "Invalid type"
 + 
 +For demonstration, the current patch uses option 2.
  
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
Line 43: Line 141:
 ===== Proposed PHP Version(s) ===== ===== Proposed PHP Version(s) =====
  
-PHP 7.NEXT, possibly 7.4.+Next PHP 7.x (7.4 now).
  
 ===== RFC Impact ===== ===== RFC Impact =====
 +
 ==== To SAPIs ==== ==== To SAPIs ====
  
-I need help here.+:?: //Help needed//
  
 ==== To Existing Extensions ==== ==== To Existing Extensions ====
  
-I need help here, but I do not think so.+:?: //Help needed//
  
 ==== To Opcache ==== ==== To Opcache ====
  
-I need help here.+:?: //Help needed// 
 + 
 +===== Unaffected PHP Functionality ===== 
 + 
 +  * The ''(unset)'' cast (always returning ''null'', deprecated in PHP 7.2 and to be removed in PHP 8.0) is not affected (i.e. the "''(?unset)''" syntax is not proposed, and will continue to cause a Parse error). 
 +  * The <php>gettype()</php> function is not affected.
  
 ===== Proposed Voting Choices ===== ===== Proposed Voting Choices =====
  
-Voting process should requires ''2/3+1'' when available.+(Each child vote result will be considered only if its parent vote passes.) 
 + 
 +  * **Accept nullable casting?**: Simple vote (Yes / No), requiring a 2/3 majority to pass. 
 +    * **Additionally accept nullable settype()?**: Simple vote (Yes / No), also requiring a 2/3 majority to pass. 
 +      * **How to handle settype($x, "?null")?**: Multi-options vote (Allow silently / Allow but Notice / Disallow with specific Warning / Disallow with the generic Warning), the option with more votes will win. 
 + 
 +(The voting period would be two weeks)
  
 ===== Patches and Tests ===== ===== Patches and Tests =====
  
-I need help here.+  * Working prototype: https://github.com/php/php-src/pull/3764 
 + 
 +===== References ===== 
 + 
 +  * PHP Manual: [[http://php.net/manual/en/language.types.type-juggling.php|Type Juggling]], [[http://php.net/manual/en/function.settype.php|settype() function]] 
 +  * PHP RFC: [[rfc:scalar_type_hints_v5|Scalar Type Declarations]], [[rfc:nullable_types|Nullable Types]] 
 +  * Initial idea and discussion: https://externals.io/message/102997 
 +  * Annoucement and discussion: https://externals.io/message/105122
rfc/nullable-casting.txt · Last modified: 2019/04/21 09:03 by guilliamxavier