Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Zend/tests/typehints/callable_basic_tests.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
The most basic callable type tests
--FILE--
<?php

class A {}

$a = function (callable() $a) {};
$a(function () {});

$a = function (callable(A) $a) {};
$a(function (A $a) {});

$a = function (callable(): A $a) {};
$a(function (): A {});

$a = function (callable(A, int) $a) {};
$a(function (...$everything) {});

$a = function (callable(): A $a) { $a(); };
$a(function &(): A { $a = new A; return $a; });

?>
--EXPECT--
126 changes: 126 additions & 0 deletions Zend/tests/typehints/callable_nested_polymorphism.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
--TEST--
Function is polymorphic if its input is equal to or less than what is required
and its output is equal or more specific than what is required.
If it has nested input callable constraints, with each level argument variance should be inverted.
--FILE--
<?php

class A {}
class B extends A {}

// Verify variance of callable parameters with related class type hints
// -------------------------------------------

function elephant(callable(callable(A)) $chipmunk) {
$chipmunk("giraffe");
};

function chipmunk(callable(B) $giraffe) {
$giraffe(new B);
}

function giraffe(A $a) {
var_dump($a);
}

elephant("chipmunk");

// Verify callable return type variance
// -------------------------------------------

function beetle(callable(): A $fly) {
var_dump($fly());
};

function fly(): B {
return new B;
}

beetle("fly");

// Verify callable: callable: ... syntax
// -------------------------------------------

function ostrich(callable(): callable(): B $pidgeotto) {
var_dump($pidgeotto()());
};

function pidgeotto(): callable(): B {
return "pidgeot";
}

function pidgeot(): B {
return new B;
}

ostrich("pidgeotto");

// Verify that type variance for related classes works accordingly to the rules
// -------------------------------------------

function hummingbird(callable(callable(callable(callable(A)))) $falcon) {
$falcon("eagle");
}

function falcon(callable(callable(callable(B))) $eagle) {
$eagle("seagull");
}

function eagle(callable(callable(A)) $seagull) {
$seagull("condor");
}

function seagull(callable(B) $condor) {
$condor(new B);
}

function condor(A $a) {
var_dump($a);
}

hummingbird("falcon");

// Verify that additional params on the second level of nesting are acceptable
// -------------------------------------------

function chimp(callable(callable(A)) $dog) {
$dog("rat");
}

function dog(callable(B, $a) $rat) {
$rat(new B, 1);
}

function rat(A $a) {
var_dump($a);
}

chimp('dog');

// Verify that variadic parameter without typehint can replace any other params
// ------------------------------------------

function snake(callable(callable(...$a)) $lion) {
$lion("tiger");
}

function lion(callable(B) $tiger) {
$tiger(new B);
}

function tiger(...$a) {
var_dump($a);
}

?>
--EXPECTF--
object(B)#%d (0) {
}
object(B)#%d (0) {
}
object(B)#%d (0) {
}
object(B)#%d (0) {
}
object(B)#%d (0) {
}
13 changes: 13 additions & 0 deletions Zend/tests/typehints/callable_omitted_args.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
Callable should be accepted if it expects less arguments than what was declared in a typehint
--FILE--
<?php

$a = function (callable(callable($a, $b)) $a) { $a(function ($a) { }); };
$a(function (callable($a, $b) $a) { $a(1, 2); });

$a = function (callable($a) $cb) { $cb(12); };
$a(function () {});

?>
--EXPECT--
17 changes: 17 additions & 0 deletions Zend/tests/typehints/callable_optional_arg_with_typehint.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
It is not possible to pass function that expects more parameters even if they are optional but have typehints
--FILE--
<?php

function foo(callable() $cb) {
$cb();
};
foo(function (int ...$a) { var_dump(1); });

?>
--EXPECTF--
Fatal error: Uncaught TypeError: Argument 1 passed to foo() must be compliant with callable(), incompatible callable(integer ...$a) given, called in %s on line %d and defined in %s:%d
Stack trace:
#0 %s(%d): foo(Object(Closure))
#1 {main}
thrown in %s on line %d
25 changes: 25 additions & 0 deletions Zend/tests/typehints/callable_optional_args.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
--TEST--
It is possible to pass function that expects more parameters if they are optional and don't have typehints
--FILE--
<?php

function foo1(callable() $cb) {
$cb();
};
foo1(function (...$a) { var_dump(1); });

function foo2(callable() $cb) {
$cb();
};
foo2(function ($a = 123) { var_dump(2); });

function foo3(callable(int $a) $cb) {
$cb(3);
};
foo3(function (int $a, $b = 0) { var_dump($a + $b); });

?>
--EXPECT--
int(1)
int(2)
int(3)
21 changes: 21 additions & 0 deletions Zend/tests/typehints/callable_wrong_arg_count.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Passing a function that expects 2 arguments where it is expected to take only one should throw a type error
--FILE--
<?php

function foo(callable($a) $cb) {

}

function boo($a, $b) {}

foo('boo');

?>
--EXPECTF--
Fatal error: Uncaught TypeError: Argument 1 passed to foo() must be compliant with callable($a), incompatible callable($a, $b) given, called in %s on line %d and defined in %s:%d
Stack trace:
#0 %s(%d): foo('boo')
#1 {main}
thrown in %s on line %d

18 changes: 9 additions & 9 deletions Zend/zend_API.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,24 +98,24 @@ typedef struct _zend_fcall_info_cache {

#define ZEND_FE_END { NULL, NULL, NULL, 0, 0 }

#define ZEND_ARG_INFO(pass_by_ref, name) { #name, NULL, 0, pass_by_ref, 0, 0 },
#define ZEND_ARG_PASS_INFO(pass_by_ref) { NULL, NULL, 0, pass_by_ref, 0, 0 },
#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, #classname, IS_OBJECT, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) { #name, NULL, IS_ARRAY, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_CALLABLE_INFO(pass_by_ref, name, allow_null) { #name, NULL, IS_CALLABLE, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) { #name, NULL, type_hint, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) { #name, NULL, 0, pass_by_ref, 0, 1 },
#define ZEND_ARG_INFO(pass_by_ref, name) { #name, NULL, NULL, 0, pass_by_ref, 0, 0 },
#define ZEND_ARG_PASS_INFO(pass_by_ref) { NULL, NULL, NULL, 0, pass_by_ref, 0, 0 },
#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, #classname, NULL, IS_OBJECT, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) { #name, NULL, NULL, IS_ARRAY, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_CALLABLE_INFO(pass_by_ref, name, allow_null) { #name, NULL, NULL, IS_CALLABLE, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) { #name, NULL, NULL, type_hint, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) { #name, NULL, NULL, 0, pass_by_ref, 0, 1 },


#define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, return_reference, required_num_args, type, class_name, allow_null) \
static const zend_internal_arg_info name[] = { \
{ (const char*)(zend_uintptr_t)(required_num_args), class_name, type, return_reference, allow_null, 0 },
{ (const char*)(zend_uintptr_t)(required_num_args), class_name, NULL, type, return_reference, allow_null, 0 },
#define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(name, type, class_name, allow_null) \
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, 0, -1, type, class_name, allow_null)

#define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args) \
static const zend_internal_arg_info name[] = { \
{ (const char*)(zend_uintptr_t)(required_num_args), NULL, 0, return_reference, 0, 0 },
{ (const char*)(zend_uintptr_t)(required_num_args), NULL, NULL, 0, return_reference, 0, 0 },
#define ZEND_BEGIN_ARG_INFO(name, _unused) \
ZEND_BEGIN_ARG_INFO_EX(name, 0, ZEND_RETURN_VALUE, -1)
#define ZEND_END_ARG_INFO() };
Expand Down
55 changes: 54 additions & 1 deletion Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,57 @@ static void zend_ast_export_class_no_header(smart_str *str, zend_ast_decl *decl,
smart_str_appends(str, "}");
}

static void zend_ast_export_callable_args_list(smart_str *str, zend_ast_list *list, int indent)
{
uint32_t i = 0;
zend_ast *ast;

while (i < list->children) {
if (i != 0) {
smart_str_appends(str, ", ");
}

ast = list->child[i];

if (ast->child[0]) {
zend_ast_export_ns_name(str, ast->child[0], 0, indent);

if (ast->child[1]) {
smart_str_appendc(str, ' ');
}
}
if (ast->attr & ZEND_PARAM_REF) {
smart_str_appendc(str, '&');
}
if (ast->attr & ZEND_PARAM_VARIADIC) {
smart_str_appends(str, "...");
}

if (ast->child[1]) {
smart_str_appendc(str, '$');
zend_ast_export_name(str, ast->child[1], 0, indent);
}

i++;
}
}

static void zend_ast_export_callable_type(smart_str *str, zend_ast *ast, int indent)
{
smart_str_appends(str, "callable");

if (ast->child[0]) {
smart_str_appendc(str, '(');
zend_ast_export_callable_args_list(str, zend_ast_get_list(ast->child[0]), indent);
smart_str_appendc(str, ')');
}

if (ast->child[1]) {
smart_str_appends(str, ": ");
zend_ast_export_ex(str, ast->child[1], 0, indent);
}
}

#define BINARY_OP(_op, _p, _pl, _pr) do { \
op = _op; \
p = _p; \
Expand Down Expand Up @@ -1150,10 +1201,12 @@ static void zend_ast_export_ex(smart_str *str, zend_ast *ast, int priority, int
case ZEND_AST_TYPE:
switch (ast->attr) {
case IS_ARRAY: APPEND_STR("array");
case IS_CALLABLE: APPEND_STR("callable");
EMPTY_SWITCH_DEFAULT_CASE();
}
break;
case ZEND_AST_TYPE_CALLABLE:
zend_ast_export_callable_type(str, ast, indent);
break;

/* 1 child node */
case ZEND_AST_VAR:
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ enum _zend_ast_kind {
ZEND_AST_USE_ELEM,
ZEND_AST_TRAIT_ALIAS,
ZEND_AST_GROUP_USE,
ZEND_AST_TYPE_CALLABLE,

/* 3 child nodes */
ZEND_AST_METHOD_CALL = 3 << ZEND_AST_NUM_CHILDREN_SHIFT,
Expand Down
Loading