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
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
--- /dev/null
+--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) {
+}
+
--- /dev/null
+--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--
+
--- /dev/null
+--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
+
--- /dev/null
+--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
+
--- /dev/null
+--TEST--
+Nullable covariant return types
+
+--FILE--
+<?php
+
+interface A {
+ function method(): ?int;
+}
+
+interface B extends A {
+ function method(): int;
+}
+
+--EXPECT--
+
--- /dev/null
+--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)
+
--- /dev/null
+--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)
+
--- /dev/null
+--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)
+
--- /dev/null
+--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
+
--- /dev/null
+--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"
+
?>
--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
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)));
&& (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);
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;
} 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) {
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);
%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
optional_type:
/* empty */ { $$ = NULL; }
- | type { $$ = $1; }
+ | type_expr { $$ = $1; }
+;
+
+type_expr:
+ type { $$ = $1; }
+ | '?' type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; }
;
type:
return_type:
/* empty */ { $$ = NULL; }
- | ':' type { $$ = $2; }
- | ':' '?' type { $$ = $3; $$->attr |= ZEND_TYPE_NULLABLE; }
+ | ':' type_expr { $$ = $2; }
;
argument_list: