mirror of
https://github.com/danog/psalm.git
synced 2024-11-27 04:45:20 +01:00
1cf5153700
* Run tests in random order Being able to run tests in any order is a pre-requisite for being able to run them in parallel. * Reset type coverage between tests, fix affected tests * Reset parser and lexer between test runs and on php version change Previously lexer was reset, but parser kept the reference to the old one, and reference to the parser was kept by StatementsProvider. This resulted in order-dependent tests - if the parser was first initialized with phpVersion set to 7.4 then arrow functions worked fine, but were failing when the parser was initially constructed with settings for 7.3 This can be demonstrated on current master by upgrading to nikic/php-parser:4.9 and running: ``` vendor/bin/phpunit --no-coverage --filter="inferredArgArrowFunction" tests/ClosureTest.php ``` Now all tests using PHP 7.4 features must set the PHP version accordingly. * Marked more tests using 7.4 syntax * Reset newline-between-annotation flag between tests * Resolve real paths before passing them to checkPaths When checkPaths is called from psalm.php the paths are resolved, so we just mimicking SUT behaviour here. * Restore newline-between-annotations in DocCommentTest * Tweak Appveyor caches * Tweak TravisCI caches * Tweak CircleCI caches * Run tests in parallel Use `vendor/bin/paratest` instead of `vendor/bin/phpunit` * Use default paratest runner on Windows WrapperRunner is not supported on Windows. * TRAVIS_TAG could be empty * Restore appveyor conditional caching
825 lines
28 KiB
PHP
825 lines
28 KiB
PHP
<?php
|
|
namespace Psalm\Tests;
|
|
|
|
use const DIRECTORY_SEPARATOR;
|
|
|
|
class ClosureTest extends TestCase
|
|
{
|
|
use Traits\InvalidCodeAnalysisTestTrait;
|
|
use Traits\ValidCodeAnalysisTestTrait;
|
|
|
|
/**
|
|
* @return iterable<string,array{string,assertions?:array<string,string>,error_levels?:string[]}>
|
|
*/
|
|
public function providerValidCodeParse()
|
|
{
|
|
return [
|
|
'byRefUseVar' => [
|
|
'<?php
|
|
/** @return void */
|
|
function run_function(\Closure $fnc) {
|
|
$fnc();
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
* @psalm-suppress MixedArgument
|
|
*/
|
|
function f() {
|
|
run_function(
|
|
/**
|
|
* @return void
|
|
*/
|
|
function() use(&$data) {
|
|
$data = 1;
|
|
}
|
|
);
|
|
echo $data;
|
|
}
|
|
|
|
f();',
|
|
],
|
|
'inferredArg' => [
|
|
'<?php
|
|
$bar = ["foo", "bar"];
|
|
|
|
$bam = array_map(
|
|
function(string $a) {
|
|
return $a . "blah";
|
|
},
|
|
$bar
|
|
);',
|
|
],
|
|
'inferredArgArrowFunction' => [
|
|
'<?php
|
|
$bar = ["foo", "bar"];
|
|
|
|
$bam = array_map(
|
|
fn(string $a) => $a . "blah",
|
|
$bar
|
|
);',
|
|
'assertions' => [],
|
|
'error_levels' => [],
|
|
'7.4'
|
|
],
|
|
'varReturnType' => [
|
|
'<?php
|
|
$add_one = function(int $a) : int {
|
|
return $a + 1;
|
|
};
|
|
|
|
$a = $add_one(1);',
|
|
'assertions' => [
|
|
'$a' => 'int',
|
|
],
|
|
],
|
|
'varReturnTypeArray' => [
|
|
'<?php
|
|
$add_one = fn(int $a) : int => $a + 1;
|
|
|
|
$a = $add_one(1);',
|
|
'assertions' => [
|
|
'$a' => 'int',
|
|
],
|
|
'error_levels' => [],
|
|
'7.4'
|
|
],
|
|
'correctParamType' => [
|
|
'<?php
|
|
$take_string = function(string $s): string { return $s; };
|
|
$take_string("string");',
|
|
],
|
|
'arrayMapClosureVar' => [
|
|
'<?php
|
|
$mirror = function(int $i) : int { return $i; };
|
|
$a = array_map($mirror, [1, 2, 3]);',
|
|
'assertions' => [
|
|
'$a' => 'array{int, int, int}',
|
|
],
|
|
],
|
|
'inlineCallableFunction' => [
|
|
'<?php
|
|
class A {
|
|
function bar(): void {
|
|
function foobar(int $a, int $b): int {
|
|
return $a > $b ? 1 : 0;
|
|
}
|
|
|
|
$arr = [5, 4, 3, 1, 2];
|
|
|
|
usort($arr, "fooBar");
|
|
}
|
|
}',
|
|
],
|
|
'closureSelf' => [
|
|
'<?php
|
|
class A
|
|
{
|
|
/**
|
|
* @var self[]
|
|
*/
|
|
private $subitems;
|
|
|
|
/**
|
|
* @param self[] $in
|
|
*/
|
|
public function __construct(array $in = [])
|
|
{
|
|
array_map(function(self $i): self { return $i; }, $in);
|
|
|
|
$this->subitems = array_map(
|
|
function(self $i): self {
|
|
return $i;
|
|
},
|
|
$in
|
|
);
|
|
}
|
|
}
|
|
|
|
new A([new A, new A]);',
|
|
],
|
|
'arrayMapVariadicClosureArg' => [
|
|
'<?php
|
|
$a = array_map(
|
|
function(int $type, string ...$args):string {
|
|
return "hello";
|
|
},
|
|
[1, 2, 3]
|
|
);',
|
|
],
|
|
'returnsTypedClosure' => [
|
|
'<?php
|
|
/**
|
|
* @param Closure(int):int $f
|
|
* @param Closure(int):int $g
|
|
*
|
|
* @return Closure(int):int
|
|
*/
|
|
function foo(Closure $f, Closure $g) : Closure {
|
|
return function (int $x) use ($f, $g) : int {
|
|
return $f($g($x));
|
|
};
|
|
}',
|
|
],
|
|
'returnsTypedClosureArrow' => [
|
|
'<?php
|
|
/**
|
|
* @param Closure(int):int $f
|
|
* @param Closure(int):int $g
|
|
*
|
|
* @return Closure(int):int
|
|
*/
|
|
function foo(Closure $f, Closure $g) : Closure {
|
|
return fn(int $x):int => $f($g($x));
|
|
}',
|
|
'assertions' => [],
|
|
'error_levels' => [],
|
|
'7.4'
|
|
],
|
|
'returnsTypedClosureWithClasses' => [
|
|
'<?php
|
|
class A {}
|
|
class B {}
|
|
class C {}
|
|
|
|
/**
|
|
* @param Closure(B):A $f
|
|
* @param Closure(C):B $g
|
|
*
|
|
* @return Closure(C):A
|
|
*/
|
|
function foo(Closure $f, Closure $g) : Closure {
|
|
return function (C $x) use ($f, $g) : A {
|
|
return $f($g($x));
|
|
};
|
|
}
|
|
|
|
$a = foo(
|
|
function(B $b) : A { return new A;},
|
|
function(C $c) : B { return new B;}
|
|
)(new C);',
|
|
'assertions' => [
|
|
'$a' => 'A',
|
|
],
|
|
],
|
|
'returnsTypedClosureWithSubclassParam' => [
|
|
'<?php
|
|
class A {}
|
|
class B {}
|
|
class C {}
|
|
class C2 extends C {}
|
|
|
|
/**
|
|
* @param Closure(B):A $f
|
|
* @param Closure(C):B $g
|
|
*
|
|
* @return Closure(C2):A
|
|
*/
|
|
function foo(Closure $f, Closure $g) : Closure {
|
|
return function (C $x) use ($f, $g) : A {
|
|
return $f($g($x));
|
|
};
|
|
}
|
|
|
|
$a = foo(
|
|
function(B $b) : A { return new A;},
|
|
function(C $c) : B { return new B;}
|
|
)(new C2);',
|
|
'assertions' => [
|
|
'$a' => 'A',
|
|
],
|
|
],
|
|
'returnsTypedClosureWithParentReturn' => [
|
|
'<?php
|
|
class A {}
|
|
class B {}
|
|
class C {}
|
|
class A2 extends A {}
|
|
|
|
/**
|
|
* @param Closure(B):A2 $f
|
|
* @param Closure(C):B $g
|
|
*
|
|
* @return Closure(C):A
|
|
*/
|
|
function foo(Closure $f, Closure $g) : Closure {
|
|
return function (C $x) use ($f, $g) : A2 {
|
|
return $f($g($x));
|
|
};
|
|
}
|
|
|
|
$a = foo(
|
|
function(B $b) : A2 { return new A2;},
|
|
function(C $c) : B { return new B;}
|
|
)(new C);',
|
|
'assertions' => [
|
|
'$a' => 'A',
|
|
],
|
|
],
|
|
'inferArrayMapReturnTypeWithoutTypehints' => [
|
|
'<?php
|
|
/**
|
|
* @param array{0:string,1:string}[] $ret
|
|
* @return array{0:string,1:int}[]
|
|
*/
|
|
function f(array $ret) : array
|
|
{
|
|
return array_map(
|
|
/**
|
|
* @param array{0:string,1:string} $row
|
|
*/
|
|
function (array $row) {
|
|
return [
|
|
strval($row[0]),
|
|
intval($row[1]),
|
|
];
|
|
},
|
|
$ret
|
|
);
|
|
}',
|
|
'assertions' => [],
|
|
'error_levels' => ['MissingClosureReturnType'],
|
|
],
|
|
'inferArrayMapReturnTypeWithTypehints' => [
|
|
'<?php
|
|
/**
|
|
* @param array{0:string,1:string}[] $ret
|
|
* @return array{0:string,1:int}[]
|
|
*/
|
|
function f(array $ret): array
|
|
{
|
|
return array_map(
|
|
/**
|
|
* @param array{0:string,1:string} $row
|
|
*/
|
|
function (array $row): array {
|
|
return [
|
|
strval($row[0]),
|
|
intval($row[1]),
|
|
];
|
|
},
|
|
$ret
|
|
);
|
|
}',
|
|
],
|
|
'invokableProperties' => [
|
|
'<?php
|
|
class A {
|
|
public function __invoke(): bool { return true; }
|
|
}
|
|
|
|
class C {
|
|
/** @var A $invokable */
|
|
private $invokable;
|
|
|
|
public function __construct(A $invokable) {
|
|
$this->invokable = $invokable;
|
|
}
|
|
|
|
public function callTheInvokableDirectly(): bool {
|
|
return ($this->invokable)();
|
|
}
|
|
|
|
public function callTheInvokableIndirectly(): bool {
|
|
$r = $this->invokable;
|
|
return $r();
|
|
}
|
|
}',
|
|
],
|
|
'PHP71-mirrorCallableParams' => [
|
|
'<?php
|
|
namespace NS;
|
|
use Closure;
|
|
/** @param Closure(int):bool $c */
|
|
function acceptsIntToBool(Closure $c): void {}
|
|
|
|
acceptsIntToBool(Closure::fromCallable(function(int $n): bool { return $n > 0; }));',
|
|
],
|
|
'singleLineClosures' => [
|
|
'<?php
|
|
$a = function() : Closure { return function() : string { return "hello"; }; };
|
|
$b = $a()();',
|
|
'assertions' => [
|
|
'$a' => 'Closure():Closure():string(hello)',
|
|
'$b' => 'string',
|
|
],
|
|
],
|
|
'voidReturningArrayMap' => [
|
|
'<?php
|
|
array_map(
|
|
function(int $i) : void {
|
|
echo $i;
|
|
},
|
|
[1, 2, 3]
|
|
);',
|
|
],
|
|
'PHP71-closureFromCallableInvokableNamedClass' => [
|
|
'<?php
|
|
namespace NS;
|
|
use Closure;
|
|
|
|
/** @param Closure(int):bool $c */
|
|
function acceptsIntToBool(Closure $c): void {}
|
|
|
|
class NamedInvokable {
|
|
public function __invoke(int $p): bool {
|
|
return $p > 0;
|
|
}
|
|
}
|
|
|
|
acceptsIntToBool(Closure::fromCallable(new NamedInvokable));',
|
|
],
|
|
'PHP71-closureFromCallableInvokableAnonymousClass' => [
|
|
'<?php
|
|
namespace NS;
|
|
use Closure;
|
|
|
|
/** @param Closure(int):bool $c */
|
|
function acceptsIntToBool(Closure $c): void {}
|
|
|
|
$anonInvokable = new class {
|
|
public function __invoke(int $p):bool {
|
|
return $p > 0;
|
|
}
|
|
};
|
|
|
|
acceptsIntToBool(Closure::fromCallable($anonInvokable));',
|
|
],
|
|
'PHP71-publicCallableFromInside' => [
|
|
'<?php
|
|
class Base {
|
|
public function publicMethod() : void {}
|
|
}
|
|
|
|
class Example extends Base {
|
|
public function test() : Closure {
|
|
return Closure::fromCallable([$this, "publicMethod"]);
|
|
}
|
|
}',
|
|
],
|
|
'PHP71-protectedCallableFromInside' => [
|
|
'<?php
|
|
class Base {
|
|
protected function protectedMethod() : void {}
|
|
}
|
|
|
|
class Example extends Base {
|
|
public function test() : Closure {
|
|
return Closure::fromCallable([$this, "protectedMethod"]);
|
|
}
|
|
}',
|
|
],
|
|
'allowClosureWithNarrowerReturn' => [
|
|
'<?php
|
|
class A {}
|
|
class B extends A {}
|
|
|
|
/**
|
|
* @param Closure():A $x
|
|
*/
|
|
function accept_closure($x) : void {
|
|
$x();
|
|
}
|
|
accept_closure(
|
|
function () : B {
|
|
return new B();
|
|
}
|
|
);',
|
|
],
|
|
'allowCallableWithWiderParam' => [
|
|
'<?php
|
|
class A {}
|
|
class B extends A {}
|
|
|
|
/**
|
|
* @param Closure(B $a):A $x
|
|
*/
|
|
function accept_closure($x) : void {
|
|
$x(new B());
|
|
}
|
|
accept_closure(
|
|
function (A $a) : A {
|
|
return $a;
|
|
}
|
|
);',
|
|
],
|
|
'allowCallableWithOptionalArg' => [
|
|
'<?php
|
|
/**
|
|
* @param Closure():int $x
|
|
*/
|
|
function accept_closure($x) : void {
|
|
$x();
|
|
}
|
|
accept_closure(
|
|
function (int $x = 5) : int {
|
|
return $x;
|
|
}
|
|
);',
|
|
],
|
|
'refineCallableTypeWithTypehint' => [
|
|
'<?php
|
|
/** @param string[][] $arr */
|
|
function foo(array $arr) : void {
|
|
array_map(
|
|
function(array $a) {
|
|
return reset($a);
|
|
},
|
|
$arr
|
|
);
|
|
}'
|
|
],
|
|
'refineCallableTypeWithoutTypehint' => [
|
|
'<?php
|
|
/** @param string[][] $arr */
|
|
function foo(array $arr) : void {
|
|
array_map(
|
|
function($a) {
|
|
return reset($a);
|
|
},
|
|
$arr
|
|
);
|
|
}'
|
|
],
|
|
'inferGeneratorReturnType' => [
|
|
'<?php
|
|
function accept(Generator $gen): void {}
|
|
|
|
accept(
|
|
(function() {
|
|
yield;
|
|
return 42;
|
|
})()
|
|
);'
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
|
|
*/
|
|
public function providerInvalidCodeParse()
|
|
{
|
|
return [
|
|
'wrongArg' => [
|
|
'<?php
|
|
$bar = ["foo", "bar"];
|
|
|
|
$bam = array_map(
|
|
function(int $a): int {
|
|
return $a + 1;
|
|
},
|
|
$bar
|
|
);',
|
|
'error_message' => 'InvalidScalarArgument',
|
|
],
|
|
'noReturn' => [
|
|
'<?php
|
|
$bar = ["foo", "bar"];
|
|
|
|
$bam = array_map(
|
|
function(string $a): string {
|
|
},
|
|
$bar
|
|
);',
|
|
'error_message' => 'InvalidReturnType',
|
|
],
|
|
'possiblyNullFunctionCall' => [
|
|
'<?php
|
|
/**
|
|
* @var Closure|null $foo
|
|
*/
|
|
$foo = null;
|
|
|
|
|
|
$foo =
|
|
/**
|
|
* @param mixed $bar
|
|
* @psalm-suppress MixedFunctionCall
|
|
*/
|
|
function ($bar) use (&$foo): string
|
|
{
|
|
if (is_array($bar)) {
|
|
return $foo($bar);
|
|
}
|
|
|
|
return $bar;
|
|
};',
|
|
'error_message' => 'MixedReturnStatement',
|
|
],
|
|
'wrongParamType' => [
|
|
'<?php
|
|
$take_string = function(string $s): string { return $s; };
|
|
$take_string(42);',
|
|
'error_message' => 'InvalidScalarArgument',
|
|
],
|
|
'missingClosureReturnType' => [
|
|
'<?php
|
|
$a = function() {
|
|
return "foo";
|
|
};',
|
|
'error_message' => 'MissingClosureReturnType',
|
|
],
|
|
'returnsTypedClosureWithBadReturnType' => [
|
|
'<?php
|
|
/**
|
|
* @param Closure(int):int $f
|
|
* @param Closure(int):int $g
|
|
*
|
|
* @return Closure(int):string
|
|
*/
|
|
function foo(Closure $f, Closure $g) : Closure {
|
|
return function (int $x) use ($f, $g) : int {
|
|
return $f($g($x));
|
|
};
|
|
}',
|
|
'error_message' => 'InvalidReturnStatement',
|
|
],
|
|
'returnsTypedCallableWithBadReturnType' => [
|
|
'<?php
|
|
/**
|
|
* @param Closure(int):int $f
|
|
* @param Closure(int):int $g
|
|
*
|
|
* @return callable(int):string
|
|
*/
|
|
function foo(Closure $f, Closure $g) : callable {
|
|
return function (int $x) use ($f, $g) : int {
|
|
return $f($g($x));
|
|
};
|
|
}',
|
|
'error_message' => 'InvalidReturnStatement',
|
|
],
|
|
'returnsTypedClosureWithBadParamType' => [
|
|
'<?php
|
|
/**
|
|
* @param Closure(int):int $f
|
|
* @param Closure(int):int $g
|
|
*
|
|
* @return Closure(string):int
|
|
*/
|
|
function foo(Closure $f, Closure $g) : Closure {
|
|
return function (int $x) use ($f, $g) : int {
|
|
return $f($g($x));
|
|
};
|
|
}',
|
|
'error_message' => 'InvalidReturnStatement',
|
|
],
|
|
'returnsTypedCallableWithBadParamType' => [
|
|
'<?php
|
|
/**
|
|
* @param Closure(int):int $f
|
|
* @param Closure(int):int $g
|
|
*
|
|
* @return callable(string):int
|
|
*/
|
|
function foo(Closure $f, Closure $g) : callable {
|
|
return function (int $x) use ($f, $g) : int {
|
|
return $f($g($x));
|
|
};
|
|
}',
|
|
'error_message' => 'InvalidReturnStatement',
|
|
],
|
|
'returnsTypedClosureWithBadCall' => [
|
|
'<?php
|
|
class A {}
|
|
class B {}
|
|
class C {}
|
|
class D {}
|
|
|
|
/**
|
|
* @param Closure(B):A $f
|
|
* @param Closure(C):B $g
|
|
*
|
|
* @return Closure(C):A
|
|
*/
|
|
function foo(Closure $f, Closure $g) : Closure {
|
|
return function (int $x) use ($f, $g) : int {
|
|
return $f($g($x));
|
|
};
|
|
}',
|
|
'error_message' => 'InvalidArgument',
|
|
],
|
|
'returnsTypedClosureWithSubclassParam' => [
|
|
'<?php
|
|
class A {}
|
|
class B {}
|
|
class C {}
|
|
class C2 extends C {}
|
|
|
|
/**
|
|
* @param Closure(B):A $f
|
|
* @param Closure(C):B $g
|
|
*
|
|
* @return Closure(C):A
|
|
*/
|
|
function foo(Closure $f, Closure $g) : Closure {
|
|
return function (C2 $x) use ($f, $g) : A {
|
|
return $f($g($x));
|
|
};
|
|
}',
|
|
'error_message' => 'LessSpecificReturnStatement',
|
|
],
|
|
'returnsTypedClosureWithSubclassReturn' => [
|
|
'<?php
|
|
class A {}
|
|
class B {}
|
|
class C {}
|
|
class A2 extends A {}
|
|
|
|
/**
|
|
* @param Closure(B):A $f
|
|
* @param Closure(C):B $g
|
|
*
|
|
* @return Closure(C):A2
|
|
*/
|
|
function foo(Closure $f, Closure $g) : Closure {
|
|
return function (C $x) use ($f, $g) : A {
|
|
return $f($g($x));
|
|
};
|
|
}',
|
|
'error_message' => 'LessSpecificReturnStatement',
|
|
],
|
|
'returnsTypedClosureFromCallable' => [
|
|
'<?php
|
|
class A {}
|
|
class B {}
|
|
class C {}
|
|
|
|
/**
|
|
* @param Closure(B):A $f
|
|
* @param Closure(C):B $g
|
|
*
|
|
* @return callable(C):A
|
|
*/
|
|
function foo(Closure $f, Closure $g) : callable {
|
|
return function (C $x) use ($f, $g) : A {
|
|
return $f($g($x));
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param Closure(B):A $f
|
|
* @param Closure(C):B $g
|
|
*
|
|
* @return Closure(C):A
|
|
*/
|
|
function bar(Closure $f, Closure $g) : Closure {
|
|
return foo($f, $g);
|
|
}',
|
|
'error_message' => 'LessSpecificReturnStatement',
|
|
],
|
|
'undefinedVariable' => [
|
|
'<?php
|
|
$a = function() use ($i) {};',
|
|
'error_message' => 'UndefinedVariable',
|
|
],
|
|
'voidReturningArrayMap' => [
|
|
'<?php
|
|
$arr = array_map(
|
|
function(int $i) : void {
|
|
echo $i;
|
|
},
|
|
[1, 2, 3]
|
|
);
|
|
|
|
foreach ($arr as $a) {
|
|
if ($a) {}
|
|
}',
|
|
'error_message' => 'TypeDoesNotContainType',
|
|
],
|
|
'PHP71-closureFromCallableInvokableNamedClassWrongArgs' => [
|
|
'<?php
|
|
namespace NS;
|
|
use Closure;
|
|
|
|
/** @param Closure(string):bool $c */
|
|
function acceptsIntToBool(Closure $c): void {}
|
|
|
|
class NamedInvokable {
|
|
public function __invoke(int $p): bool {
|
|
return $p > 0;
|
|
}
|
|
}
|
|
|
|
acceptsIntToBool(Closure::fromCallable(new NamedInvokable));',
|
|
'error_message' => 'InvalidScalarArgument',
|
|
],
|
|
'undefinedClassForCallable' => [
|
|
'<?php
|
|
class Foo {
|
|
public function __construct(UndefinedClass $o) {}
|
|
}
|
|
new Foo(function() : void {});',
|
|
'error_message' => 'UndefinedClass',
|
|
],
|
|
'useDuplicateName' => [
|
|
'<?php
|
|
$foo = "bar";
|
|
|
|
$a = function (string $foo) use ($foo) : string {
|
|
return $foo;
|
|
};',
|
|
'error_message' => 'DuplicateParam',
|
|
],
|
|
'PHP71-privateCallable' => [
|
|
'<?php
|
|
class Base {
|
|
private function privateMethod() : void {}
|
|
}
|
|
|
|
class Example extends Base {
|
|
public function test() : Closure {
|
|
return Closure::fromCallable([$this, "privateMethod"]);
|
|
}
|
|
}',
|
|
'error_message' => 'InvalidArgument',
|
|
],
|
|
'prohibitCallableWithRequiredArg' => [
|
|
'<?php
|
|
/**
|
|
* @param Closure():int $x
|
|
*/
|
|
function accept_closure($x) : void {
|
|
$x();
|
|
}
|
|
accept_closure(
|
|
function (int $x) : int {
|
|
return $x;
|
|
}
|
|
);',
|
|
'error_message' => 'InvalidArgument',
|
|
],
|
|
'useClosureDocblockType' => [
|
|
'<?php
|
|
class A {}
|
|
class B extends A {}
|
|
|
|
function takesA(A $_a) : void {}
|
|
function takesB(B $_b) : void {}
|
|
|
|
$getAButReallyB = /** @return A */ function() {
|
|
return new B;
|
|
};
|
|
|
|
takesA($getAButReallyB());
|
|
takesB($getAButReallyB());',
|
|
'error_message' => 'ArgumentTypeCoercion - src' . DIRECTORY_SEPARATOR . 'somefile.php:13:28 - Argument 1 of takesB expects B, parent type A provided',
|
|
],
|
|
'closureByRefUseToMixed' => [
|
|
'<?php
|
|
function assertInt(int $int): int {
|
|
$s = static function() use(&$int): void {
|
|
$int = "42";
|
|
};
|
|
|
|
$s();
|
|
|
|
return $int;
|
|
}',
|
|
'error_message' => 'MixedReturnStatement'
|
|
],
|
|
];
|
|
}
|
|
}
|