]> granicus.if.org Git - php/commitdiff
Add nullable parameter types
authorLevi Morrison <levim@php.net>
Thu, 28 Apr 2016 21:26:57 +0000 (15:26 -0600)
committerLevi Morrison <levim@php.net>
Thu, 5 May 2016 17:53:32 +0000 (11:53 -0600)
This works off of Dmitry's commit for nullable return types

17 files changed:
Zend/tests/bug71428.1.phpt
Zend/tests/bug71428.3.phpt
Zend/tests/bug72119.phpt
Zend/tests/nullable_types/array.phpt [new file with mode: 0644]
Zend/tests/nullable_types/contravariant_nullable_param_succeeds.phpt [new file with mode: 0644]
Zend/tests/nullable_types/contravariant_nullable_return_fails.phpt [new file with mode: 0644]
Zend/tests/nullable_types/covariant_nullable_param_fails.phpt [new file with mode: 0644]
Zend/tests/nullable_types/covariant_nullable_return_succeds.phpt [new file with mode: 0644]
Zend/tests/nullable_types/float.phpt [new file with mode: 0644]
Zend/tests/nullable_types/int.phpt [new file with mode: 0644]
Zend/tests/nullable_types/invariant_param_and_return_succeeds.phpt [new file with mode: 0644]
Zend/tests/nullable_types/nullable_type_parameters_do_not_have_default_value.phpt [new file with mode: 0644]
Zend/tests/nullable_types/string.phpt [new file with mode: 0644]
Zend/tests/variadic/adding_additional_optional_parameter_error.phpt
Zend/zend_compile.c
Zend/zend_inheritance.c
Zend/zend_language_parser.y

index fbf342380ffedc745495addfb2a6a9bd3649217e..064e8cfa517ff1e6eb3a61be58329eb672db62a8 100644 (file)
@@ -11,5 +11,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
 
index 65d397052e4312501984a85a8e593c87e7857456..53e5129d89be01746d59802e25bebf87f0bb1b43 100644 (file)
@@ -9,5 +9,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
 
index b8f070a25af819585cc5ce546266223f233c4e7c..eb9b4a73a7899c70caa92ef00018acf3ed89838b 100644 (file)
@@ -16,3 +16,4 @@ echo "OK\n";
 ?>
 --EXPECT--
 OK
+
diff --git a/Zend/tests/nullable_types/array.phpt b/Zend/tests/nullable_types/array.phpt
new file mode 100644 (file)
index 0000000..7b0a2ed
--- /dev/null
@@ -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) {
+}
+
diff --git a/Zend/tests/nullable_types/contravariant_nullable_param_succeeds.phpt b/Zend/tests/nullable_types/contravariant_nullable_param_succeeds.phpt
new file mode 100644 (file)
index 0000000..f4d1e31
--- /dev/null
@@ -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--
+
diff --git a/Zend/tests/nullable_types/contravariant_nullable_return_fails.phpt b/Zend/tests/nullable_types/contravariant_nullable_return_fails.phpt
new file mode 100644 (file)
index 0000000..c9be479
--- /dev/null
@@ -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
+
diff --git a/Zend/tests/nullable_types/covariant_nullable_param_fails.phpt b/Zend/tests/nullable_types/covariant_nullable_param_fails.phpt
new file mode 100644 (file)
index 0000000..65b2858
--- /dev/null
@@ -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
+
diff --git a/Zend/tests/nullable_types/covariant_nullable_return_succeds.phpt b/Zend/tests/nullable_types/covariant_nullable_return_succeds.phpt
new file mode 100644 (file)
index 0000000..5776f9b
--- /dev/null
@@ -0,0 +1,16 @@
+--TEST--
+Nullable covariant return types
+
+--FILE--
+<?php
+
+interface A {
+    function method(): ?int;
+}
+
+interface B extends A {
+    function method(): int;
+}
+
+--EXPECT--
+
diff --git a/Zend/tests/nullable_types/float.phpt b/Zend/tests/nullable_types/float.phpt
new file mode 100644 (file)
index 0000000..8e44524
--- /dev/null
@@ -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)
+
diff --git a/Zend/tests/nullable_types/int.phpt b/Zend/tests/nullable_types/int.phpt
new file mode 100644 (file)
index 0000000..ec75132
--- /dev/null
@@ -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)
+
diff --git a/Zend/tests/nullable_types/invariant_param_and_return_succeeds.phpt b/Zend/tests/nullable_types/invariant_param_and_return_succeeds.phpt
new file mode 100644 (file)
index 0000000..0542e52
--- /dev/null
@@ -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)
+
diff --git a/Zend/tests/nullable_types/nullable_type_parameters_do_not_have_default_value.phpt b/Zend/tests/nullable_types/nullable_type_parameters_do_not_have_default_value.phpt
new file mode 100644 (file)
index 0000000..13b25e0
--- /dev/null
@@ -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
+
diff --git a/Zend/tests/nullable_types/string.phpt b/Zend/tests/nullable_types/string.phpt
new file mode 100644 (file)
index 0000000..ffc6591
--- /dev/null
@@ -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"
+
index da96264609d815301d889e5c14125e961e1b1c55..4c6f36f10b531f5935d853b1a30f18c87e04d75c 100644 (file)
@@ -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
index fe81aadcf8930d893ff8755dfd9265561cf4d773..b5f91c4495d07aa5f28ba3e901e4d6aa461acafb 100644 (file)
@@ -4866,7 +4866,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)));
@@ -4999,9 +4999,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);
 
index df7dbfd63eea773611488af3de583911086f49d4..de9b63e342676bc149cb41cabb70b9ef05dba6d3 100644 (file)
@@ -355,6 +355,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;
@@ -495,8 +500,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) {
@@ -510,9 +513,6 @@ static ZEND_COLD zend_string *zend_get_function_declaration(const zend_function
 
        if (fptr->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
                smart_str_appends(&str, ": ");
-               if (fptr->common.arg_info[-1].allow_null) {
-                       smart_str_appendc(&str, '?');
-               }
                zend_append_type_hint(&str, fptr, fptr->common.arg_info - 1, 1);
        }
        smart_str_0(&str);
index c995e5fd4dee9cfcb774d519d2d6691fbcb46c0d..7682d69b6879612c06e6a8e1081a4bf82d95fab5 100644 (file)
@@ -252,7 +252,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 array_pair_list non_empty_array_pair_list
 %type <ast> assignment_list unkeyed_assignment_list keyed_assignment_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,8 +660,7 @@ type:
 
 return_type:
                /* empty */     { $$ = NULL; }
-       |       ':' type        { $$ = $2; }
-       |       ':' '?' type    { $$ = $3; $$->attr |= ZEND_TYPE_NULLABLE; }
+       |       ':' type_expr   { $$ = $2; }
 ;
 
 argument_list: