mirror of
https://github.com/php/php-src.git
synced 2025-08-16 05:58:45 +02:00
Merge branch 'nullable_types' of github.com:morrisonlevi/php-src
* 'nullable_types' of github.com:morrisonlevi/php-src: Fix bug #71428 Add nullable parameter types Implement nullable return types.
This commit is contained in:
commit
0cdbabe558
22 changed files with 262 additions and 19 deletions
1
NEWS
1
NEWS
|
@ -3,6 +3,7 @@ PHP NEWS
|
|||
?? ??? 2016, PHP 7.1.0
|
||||
|
||||
- Core:
|
||||
. Added nullable types. (Levi, Dmitry)
|
||||
. Added DFA optimization framework based on e-SSA form. (Dmitry, Nikita)
|
||||
. Added specialized opcode handlers (e.g. ZEND_ADD_LONG_NO_OVERFLOW).
|
||||
(Dmitry)
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
--TEST--
|
||||
bug #71428.1: inheritance with null default values
|
||||
--XFAIL--
|
||||
This is a BC break
|
||||
--FILE--
|
||||
<?php
|
||||
class A {
|
||||
|
@ -11,5 +9,5 @@ class B extends A {
|
|||
public function m(array $a = []) {}
|
||||
}
|
||||
--EXPECTF--
|
||||
Warning: Declaration of B::m(array $a = Array) should be compatible with A::m(array $a = NULL) in %sbug71428.1.php on line 7
|
||||
Warning: Declaration of B::m(array $a = Array) should be compatible with A::m(?array $a = NULL) in %sbug71428.1.php on line 7
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
--TEST--
|
||||
bug #71428: Validation type inheritance with = NULL
|
||||
--XFAIL--
|
||||
This is a BC break
|
||||
--FILE--
|
||||
<?php
|
||||
class A { }
|
||||
|
@ -9,5 +7,5 @@ class B { public function m(A $a = NULL, $n) { echo "B.m";} };
|
|||
class C extends B { public function m(A $a , $n) { echo "C.m";} };
|
||||
?>
|
||||
--EXPECTF--
|
||||
Warning: Declaration of C::m(A $a, $n) should be compatible with B::m(A $a = NULL, $n) in %sbug71428.3.php on line 4
|
||||
Warning: Declaration of C::m(A $a, $n) should be compatible with B::m(?A $a, $n) in %sbug71428.3.php on line 4
|
||||
|
||||
|
|
|
@ -14,5 +14,6 @@ class Hello implements Foo {
|
|||
}
|
||||
echo "OK\n";
|
||||
?>
|
||||
--EXPECT--
|
||||
OK
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of Hello::bar(array $baz = Array) must be compatible with Foo::bar(?array $baz = NULL) in %s on line %d
|
||||
|
||||
|
|
17
Zend/tests/nullable_types/array.phpt
Normal file
17
Zend/tests/nullable_types/array.phpt
Normal file
|
@ -0,0 +1,17 @@
|
|||
--TEST--
|
||||
Explicitly nullable array type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function _array_(?array $v): ?array {
|
||||
return $v;
|
||||
}
|
||||
|
||||
var_dump(_array_(null));
|
||||
var_dump(_array_([]));
|
||||
|
||||
--EXPECT--
|
||||
NULL
|
||||
array(0) {
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
--TEST--
|
||||
Subtype can add nullability to a parameter (contravariance)
|
||||
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A {
|
||||
function method(int $p);
|
||||
}
|
||||
|
||||
class B implements A {
|
||||
function method(?int $p) { }
|
||||
}
|
||||
|
||||
$b = new B();
|
||||
$b->method(null);
|
||||
|
||||
--EXPECT--
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
--TEST--
|
||||
Return type cannot add nullability (contravariance)
|
||||
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A {
|
||||
function method(): int;
|
||||
}
|
||||
|
||||
interface B extends A {
|
||||
function method(): ?int;
|
||||
}
|
||||
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of B::method(): ?int must be compatible with A::method(): int in %s on line %d
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
--TEST--
|
||||
Subtype cannot remove nullable parameter (covariance)
|
||||
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A {
|
||||
function method(?int $p);
|
||||
}
|
||||
|
||||
class B implements A {
|
||||
function method(int $p) { }
|
||||
}
|
||||
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of B::method(int $p) must be compatible with A::method(?int $p) in %s on line %d
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
--TEST--
|
||||
Nullable covariant return types
|
||||
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A {
|
||||
function method(): ?int;
|
||||
}
|
||||
|
||||
interface B extends A {
|
||||
function method(): int;
|
||||
}
|
||||
|
||||
--EXPECT--
|
||||
|
16
Zend/tests/nullable_types/float.phpt
Normal file
16
Zend/tests/nullable_types/float.phpt
Normal file
|
@ -0,0 +1,16 @@
|
|||
--TEST--
|
||||
Explicitly nullable float type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function _float_(?float $v): ?float {
|
||||
return $v;
|
||||
}
|
||||
|
||||
var_dump(_float_(null));
|
||||
var_dump(_float_(1.3));
|
||||
|
||||
--EXPECT--
|
||||
NULL
|
||||
float(1.3)
|
||||
|
16
Zend/tests/nullable_types/int.phpt
Normal file
16
Zend/tests/nullable_types/int.phpt
Normal file
|
@ -0,0 +1,16 @@
|
|||
--TEST--
|
||||
Explicitly nullable int type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function _int_(?int $v): ?int {
|
||||
return $v;
|
||||
}
|
||||
|
||||
var_dump(_int_(null));
|
||||
var_dump(_int_(1));
|
||||
|
||||
--EXPECT--
|
||||
NULL
|
||||
int(1)
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
--TEST--
|
||||
Invariant parameter and return types work with nullable types
|
||||
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A {
|
||||
function method(?int $i): ?int;
|
||||
}
|
||||
|
||||
class B implements A {
|
||||
function method(?int $i): ?int {
|
||||
return $i;
|
||||
}
|
||||
}
|
||||
|
||||
$b = new B();
|
||||
var_dump($b->method(null));
|
||||
var_dump($b->method(1));
|
||||
|
||||
--EXPECT--
|
||||
NULL
|
||||
int(1)
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
--TEST--
|
||||
Explicit nullable types do not imply a default value
|
||||
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function f(?callable $p) {}
|
||||
|
||||
f();
|
||||
|
||||
--EXPECTF--
|
||||
Fatal error: Uncaught TypeError: Argument 1 passed to f() must be callable, none given, called in %s on line %d and defined in %s:%d
|
||||
Stack trace:
|
||||
#%d %s
|
||||
#%d %s
|
||||
thrown in %s on line %d
|
||||
|
16
Zend/tests/nullable_types/string.phpt
Normal file
16
Zend/tests/nullable_types/string.phpt
Normal file
|
@ -0,0 +1,16 @@
|
|||
--TEST--
|
||||
Explicitly nullable string type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function _string_(?string $v): ?string {
|
||||
return $v;
|
||||
}
|
||||
|
||||
var_dump(_string_(null));
|
||||
var_dump(_string_("php"));
|
||||
|
||||
--EXPECT--
|
||||
NULL
|
||||
string(3) "php"
|
||||
|
23
Zend/tests/return_types/030.phpt
Normal file
23
Zend/tests/return_types/030.phpt
Normal file
|
@ -0,0 +1,23 @@
|
|||
--TEST--
|
||||
Nullable return value
|
||||
--FILE--
|
||||
<?php
|
||||
function foo($x) : ?array {
|
||||
return $x;
|
||||
}
|
||||
|
||||
foo([]);
|
||||
echo "ok\n";
|
||||
foo(null);
|
||||
echo "ok\n";
|
||||
foo(0);
|
||||
?>
|
||||
--EXPECTF--
|
||||
ok
|
||||
ok
|
||||
|
||||
Fatal error: Uncaught TypeError: Return value of foo() must be of the type array, integer returned in %s030.php:3
|
||||
Stack trace:
|
||||
#0 %s030.php(10): foo(0)
|
||||
#1 {main}
|
||||
thrown in %s030.php on line 3
|
14
Zend/tests/return_types/031.phpt
Normal file
14
Zend/tests/return_types/031.phpt
Normal file
|
@ -0,0 +1,14 @@
|
|||
--TEST--
|
||||
Nullable return type inheritance rules (non-nullable and nullable)
|
||||
--FILE--
|
||||
<?php
|
||||
class A {
|
||||
function foo(): int {}
|
||||
}
|
||||
class B extends A {
|
||||
function foo(): ?int {}
|
||||
}
|
||||
?>
|
||||
DONE
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of B::foo(): ?int must be compatible with A::foo(): int in %s031.php on line 7
|
14
Zend/tests/return_types/032.phpt
Normal file
14
Zend/tests/return_types/032.phpt
Normal file
|
@ -0,0 +1,14 @@
|
|||
--TEST--
|
||||
Nullable return type inheritance rules (nullable and non-nullable)
|
||||
--FILE--
|
||||
<?php
|
||||
class A {
|
||||
function foo(): ?int {}
|
||||
}
|
||||
class B extends A {
|
||||
function foo(): int {}
|
||||
}
|
||||
?>
|
||||
DONE
|
||||
--EXPECT--
|
||||
DONE
|
|
@ -13,4 +13,4 @@ class MySQL implements DB {
|
|||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of MySQL::query($query, int $extraParam = NULL, string ...$params) must be compatible with DB::query($query, string ...$params) in %s on line %d
|
||||
Fatal error: Declaration of MySQL::query($query, ?int $extraParam = NULL, string ...$params) must be compatible with DB::query($query, string ...$params) in %s on line %d
|
||||
|
|
|
@ -4889,7 +4889,7 @@ static void zend_compile_typename(zend_ast *ast, zend_arg_info *arg_info) /* {{{
|
|||
zend_uchar type = zend_lookup_builtin_type_by_name(class_name);
|
||||
|
||||
if (type != 0) {
|
||||
if (ast->attr != ZEND_NAME_NOT_FQ) {
|
||||
if ((ast->attr & ZEND_NAME_NOT_FQ) != ZEND_NAME_NOT_FQ) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR,
|
||||
"Scalar type declaration '%s' must be unqualified",
|
||||
ZSTR_VAL(zend_string_tolower(class_name)));
|
||||
|
@ -4929,6 +4929,11 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */
|
|||
arg_infos->allow_null = 0;
|
||||
arg_infos->class_name = NULL;
|
||||
|
||||
if (return_type_ast->attr & ZEND_TYPE_NULLABLE) {
|
||||
arg_infos->allow_null = 1;
|
||||
return_type_ast->attr &= ~ZEND_TYPE_NULLABLE;
|
||||
}
|
||||
|
||||
zend_compile_typename(return_type_ast, arg_infos);
|
||||
|
||||
arg_infos++;
|
||||
|
@ -5017,9 +5022,10 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */
|
|||
&& (Z_TYPE(default_node.u.constant) == IS_NULL
|
||||
|| (Z_TYPE(default_node.u.constant) == IS_CONSTANT
|
||||
&& strcasecmp(Z_STRVAL(default_node.u.constant), "NULL") == 0));
|
||||
zend_bool is_explicitly_nullable = (type_ast->attr & ZEND_TYPE_NULLABLE) == ZEND_TYPE_NULLABLE;
|
||||
|
||||
op_array->fn_flags |= ZEND_ACC_HAS_TYPE_HINTS;
|
||||
arg_info->allow_null = has_null_default;
|
||||
arg_info->allow_null = has_null_default || is_explicitly_nullable;
|
||||
|
||||
zend_compile_typename(type_ast, arg_info);
|
||||
|
||||
|
|
|
@ -839,6 +839,8 @@ ZEND_API void zend_assert_valid_class_name(const zend_string *const_name);
|
|||
#define ZEND_NAME_NOT_FQ 1
|
||||
#define ZEND_NAME_RELATIVE 2
|
||||
|
||||
#define ZEND_TYPE_NULLABLE (1<<8)
|
||||
|
||||
/* var status for backpatching */
|
||||
#define BP_VAR_R 0
|
||||
#define BP_VAR_W 1
|
||||
|
|
|
@ -319,13 +319,11 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c
|
|||
return 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// This introduces BC break described at https://bugs.php.net/bug.php?id=72119
|
||||
if (proto_arg_info->type_hint && proto_arg_info->allow_null && !fe_arg_info->allow_null) {
|
||||
/* incompatible nullability */
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* by-ref constraints on arguments are invariant */
|
||||
if (fe_arg_info->pass_by_reference != proto_arg_info->pass_by_reference) {
|
||||
|
@ -344,6 +342,10 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c
|
|||
if (!zend_do_perform_type_hint_check(fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (fe->common.arg_info[-1].allow_null && !proto->common.arg_info[-1].allow_null) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
@ -351,6 +353,11 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c
|
|||
|
||||
static ZEND_COLD void zend_append_type_hint(smart_str *str, const zend_function *fptr, zend_arg_info *arg_info, int return_hint) /* {{{ */
|
||||
{
|
||||
|
||||
if (arg_info->type_hint != IS_UNDEF && arg_info->allow_null) {
|
||||
smart_str_appendc(str, '?');
|
||||
}
|
||||
|
||||
if (arg_info->class_name) {
|
||||
const char *class_name;
|
||||
size_t class_name_len;
|
||||
|
@ -491,8 +498,6 @@ static ZEND_COLD zend_string *zend_get_function_declaration(const zend_function
|
|||
} else {
|
||||
smart_str_appends(&str, "NULL");
|
||||
}
|
||||
} else if (arg_info->type_hint && arg_info->allow_null) {
|
||||
smart_str_appends(&str, " = NULL");
|
||||
}
|
||||
|
||||
if (++i < num_args) {
|
||||
|
@ -590,7 +595,8 @@ static void do_inheritance_check_on_method(zend_function *child, zend_function *
|
|||
error_verb = "must";
|
||||
} else if ((parent->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) &&
|
||||
(!(child->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) ||
|
||||
!zend_do_perform_type_hint_check(child, child->common.arg_info - 1, parent, parent->common.arg_info - 1))) {
|
||||
!zend_do_perform_type_hint_check(child, child->common.arg_info - 1, parent, parent->common.arg_info - 1) ||
|
||||
(child->common.arg_info[-1].allow_null && !parent->common.arg_info[-1].allow_null))) {
|
||||
error_level = E_COMPILE_ERROR;
|
||||
error_verb = "must";
|
||||
} else {
|
||||
|
|
|
@ -251,7 +251,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
|
|||
%type <ast> ctor_arguments alt_if_stmt_without_else trait_adaptation_list lexical_vars
|
||||
%type <ast> lexical_var_list encaps_list
|
||||
%type <ast> array_pair non_empty_array_pair_list array_pair_list
|
||||
%type <ast> isset_variable type return_type
|
||||
%type <ast> isset_variable type return_type type_expr
|
||||
%type <ast> identifier
|
||||
|
||||
%type <num> returns_ref function is_reference is_variadic variable_modifiers
|
||||
|
@ -644,7 +644,12 @@ parameter:
|
|||
|
||||
optional_type:
|
||||
/* empty */ { $$ = NULL; }
|
||||
| type { $$ = $1; }
|
||||
| type_expr { $$ = $1; }
|
||||
;
|
||||
|
||||
type_expr:
|
||||
type { $$ = $1; }
|
||||
| '?' type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; }
|
||||
;
|
||||
|
||||
type:
|
||||
|
@ -655,7 +660,7 @@ type:
|
|||
|
||||
return_type:
|
||||
/* empty */ { $$ = NULL; }
|
||||
| ':' type { $$ = $2; }
|
||||
| ':' type_expr { $$ = $2; }
|
||||
;
|
||||
|
||||
argument_list:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue