Hi
As quickly mentioned in the '$arr = array('Hello', 'world'); $arr();'
thread[1], we are hitting the need for a callable typehint.
See attached patch+phpt; Any objections to include it in 5.4?
-Hannes
[1] http://php.markmail.org/message/gdas65h3im52sleg
Index: Zend/zend.h
===================================================================
--- Zend/zend.h (revision 311867)
+++ Zend/zend.h (working copy)
@@ -573,6 +573,7 @@
#define IS_RESOURCE 7
#define IS_CONSTANT 8
#define IS_CONSTANT_ARRAY 9
+#define IS_CALLABLE 10
/* Ugly hack to support constants as static array indices */
#define IS_CONSTANT_TYPE_MASK 0x00f
Index: Zend/zend_execute.c
===================================================================
--- Zend/zend_execute.c (revision 311867)
+++ Zend/zend_execute.c (working copy)
@@ -626,13 +626,24 @@
need_msg = zend_verify_arg_class_kind(cur_arg_info, fetch_type, &class_name, &ce TSRMLS_CC);
return zend_verify_arg_error(E_RECOVERABLE_ERROR, zf, arg_num, need_msg, class_name, zend_zval_type_name(arg), "" TSRMLS_CC);
}
- } else if (cur_arg_info->type_hint && cur_arg_info->type_hint == IS_ARRAY) {
- if (!arg) {
- return zend_verify_arg_error(E_RECOVERABLE_ERROR, zf, arg_num, "be of the type array", "", "none", "" TSRMLS_CC);
- }
+ } else if (cur_arg_info->type_hint) {
+ switch(cur_arg_info->type_hint) {
+ case IS_ARRAY:
+ if (!arg) {
+ return zend_verify_arg_error(E_RECOVERABLE_ERROR, zf, arg_num, "be of the type array", "", "none", "" TSRMLS_CC);
+ }
- if (Z_TYPE_P(arg) != IS_ARRAY && (Z_TYPE_P(arg) != IS_NULL || !cur_arg_info->allow_null)) {
- return zend_verify_arg_error(E_RECOVERABLE_ERROR, zf, arg_num, "be of the type array", "", zend_zval_type_name(arg), "" TSRMLS_CC);
+ if (Z_TYPE_P(arg) != IS_ARRAY && (Z_TYPE_P(arg) != IS_NULL || !cur_arg_info->allow_null)) {
+ return zend_verify_arg_error(E_RECOVERABLE_ERROR, zf, arg_num, "be of the type array", "", zend_zval_type_name(arg), "" TSRMLS_CC);
+ }
+ break;
+ case IS_CALLABLE:
+ if (!zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL) && (Z_TYPE_P(arg) != IS_NULL || !cur_arg_info->allow_null)) {
+ return zend_verify_arg_error(E_RECOVERABLE_ERROR, zf, arg_num, "be callable", "", zend_zval_type_name(arg), "" TSRMLS_CC);
+ }
+ break;
+ default:
+ zend_error(E_ERROR, "Unknown typehint");
}
}
return 1;
Index: Zend/zend_language_scanner.l
===================================================================
--- Zend/zend_language_scanner.l (revision 311867)
+++ Zend/zend_language_scanner.l (working copy)
@@ -1310,6 +1310,10 @@
return T_ARRAY;
}
+<ST_IN_SCRIPTING>"callable" {
+ return T_CALLABLE;
+}
+
<ST_IN_SCRIPTING>"++" {
return T_INC;
}
Index: Zend/zend_compile.c
===================================================================
--- Zend/zend_compile.c (revision 311867)
+++ Zend/zend_compile.c (working copy)
@@ -1849,28 +1849,40 @@
cur_arg_info->allow_null = 0;
if (class_type->u.constant.type != IS_NULL) {
- cur_arg_info->type_hint = IS_OBJECT;
- if (ZEND_FETCH_CLASS_DEFAULT == zend_get_class_fetch_type(Z_STRVAL(class_type->u.constant), Z_STRLEN(class_type->u.constant))) {
- zend_resolve_class_name(class_type, &opline->extended_value, 1 TSRMLS_CC);
- }
- class_type->u.constant.value.str.val = zend_new_interned_string(class_type->u.constant.value.str.val, class_type->u.constant.value.str.len + 1, 1 TSRMLS_CC);
- cur_arg_info->class_name = class_type->u.constant.value.str.val;
- cur_arg_info->class_name_len = class_type->u.constant.value.str.len;
- if (op == ZEND_RECV_INIT) {
- if (Z_TYPE(initialization->u.constant) == IS_NULL || (Z_TYPE(initialization->u.constant) == IS_CONSTANT && !strcasecmp(Z_STRVAL(initialization->u.constant), "NULL"))) {
- cur_arg_info->allow_null = 1;
- } else {
- zend_error(E_COMPILE_ERROR, "Default value for parameters with a class type hint can only be NULL");
+ if (class_type->u.constant.type == IS_ARRAY) {
+ cur_arg_info->type_hint = IS_ARRAY;
+ if (op == ZEND_RECV_INIT) {
+ if (Z_TYPE(initialization->u.constant) == IS_NULL || (Z_TYPE(initialization->u.constant) == IS_CONSTANT && !strcasecmp(Z_STRVAL(initialization->u.constant), "NULL"))) {
+ cur_arg_info->allow_null = 1;
+ } else if (Z_TYPE(initialization->u.constant) != IS_ARRAY && Z_TYPE(initialization->u.constant) != IS_CONSTANT_ARRAY) {
+ zend_error(E_COMPILE_ERROR, "Default value for parameters with array type hint can only be an array or NULL");
+ }
}
- }
- } else {
- cur_arg_info->type_hint = IS_ARRAY;
- if (op == ZEND_RECV_INIT) {
- if (Z_TYPE(initialization->u.constant) == IS_NULL || (Z_TYPE(initialization->u.constant) == IS_CONSTANT && !strcasecmp(Z_STRVAL(initialization->u.constant), "NULL"))) {
- cur_arg_info->allow_null = 1;
- } else if (Z_TYPE(initialization->u.constant) != IS_ARRAY && Z_TYPE(initialization->u.constant) != IS_CONSTANT_ARRAY) {
- zend_error(E_COMPILE_ERROR, "Default value for parameters with array type hint can only be an array or NULL");
+ } else if (class_type->u.constant.type == IS_CALLABLE) {
+ char *callable_name;
+ cur_arg_info->type_hint = IS_CALLABLE;
+ if (op == ZEND_RECV_INIT) {
+ if (Z_TYPE(initialization->u.constant) == IS_NULL || (Z_TYPE(initialization->u.constant) == IS_CONSTANT && !strcasecmp(Z_STRVAL(initialization->u.constant), "NULL"))) {
+ cur_arg_info->allow_null = 1;
+ } else {
+ zend_error(E_COMPILE_ERROR, "Default value for parameters with callable type hint can only be NULL");
+ }
}
+ } else {
+ cur_arg_info->type_hint = IS_OBJECT;
+ if (ZEND_FETCH_CLASS_DEFAULT == zend_get_class_fetch_type(Z_STRVAL(class_type->u.constant), Z_STRLEN(class_type->u.constant))) {
+ zend_resolve_class_name(class_type, &opline->extended_value, 1 TSRMLS_CC);
+ }
+ class_type->u.constant.value.str.val = zend_new_interned_string(class_type->u.constant.value.str.val, class_type->u.constant.value.str.len + 1, 1 TSRMLS_CC);
+ cur_arg_info->class_name = class_type->u.constant.value.str.val;
+ cur_arg_info->class_name_len = class_type->u.constant.value.str.len;
+ if (op == ZEND_RECV_INIT) {
+ if (Z_TYPE(initialization->u.constant) == IS_NULL || (Z_TYPE(initialization->u.constant) == IS_CONSTANT && !strcasecmp(Z_STRVAL(initialization->u.constant), "NULL"))) {
+ cur_arg_info->allow_null = 1;
+ } else {
+ zend_error(E_COMPILE_ERROR, "Default value for parameters with a class type hint can only be NULL");
+ }
+ }
}
}
}
Index: Zend/zend_language_parser.y
===================================================================
--- Zend/zend_language_parser.y (revision 311867)
+++ Zend/zend_language_parser.y (working copy)
@@ -130,6 +130,7 @@
%token T_DOUBLE_ARROW
%token T_LIST
%token T_ARRAY
+%token T_CALLABLE
%token T_CLASS_C
%token T_METHOD_C
%token T_FUNC_C
@@ -466,7 +467,8 @@
optional_class_type:
/* empty */ { $$.op_type = IS_UNUSED; }
- | T_ARRAY { $$.op_type = IS_CONST; Z_TYPE($$.u.constant)=IS_NULL; }
+ | T_ARRAY { $$.op_type = IS_CONST; Z_TYPE($$.u.constant)=IS_ARRAY; }
+ | T_CALLABLE { $$.op_type = IS_CONST; Z_TYPE($$.u.constant)=IS_CALLABLE; }
| fully_qualified_class_name { $$ = $1; }
;
Index: Zend/tests/callable_type_hint_001.phpt
===================================================================
--- Zend/tests/callable_type_hint_001.phpt (revision 0)
+++ Zend/tests/callable_type_hint_001.phpt (revision 0)
@@ -0,0 +1,40 @@
+--TEST--
+callable type hint#001
+--FILE--
+<?php
+
+class bar {
+ function baz() {}
+ static function foo() {}
+}
+function foo(callable $bar) {
+ var_dump($bar);
+}
+$closure = function () {};
+
+foo("strpos");
+foo("foo");
+foo(array("bar", "baz"));
+foo(array("bar", "foo"));
+foo($closure);
+--EXPECTF--
+string(6) "strpos"
+string(3) "foo"
+
+Strict Standards: Non-static method bar::baz() should not be called statically in %scallable_type_hint_001.php on line %d
+array(2) {
+ [0]=>
+ string(3) "bar"
+ [1]=>
+ string(3) "baz"
+}
+array(2) {
+ [0]=>
+ string(3) "bar"
+ [1]=>
+ string(3) "foo"
+}
+object(Closure)#%d (0) {
+}
+
+