,error_levels?:string[]}> */ public function providerValidCodeParse(): iterable { return [ 'byRefUseVar' => [ ' [ ' [ ' $a . "blah", $bar );', 'assertions' => [], 'error_levels' => [], '7.4' ], 'varReturnType' => [ ' [ '$a' => 'int', ], ], 'varReturnTypeArray' => [ ' $a + 1; $a = $add_one(1);', 'assertions' => [ '$a' => 'int', ], 'error_levels' => [], '7.4' ], 'correctParamType' => [ ' [ ' [ '$a' => 'array{int, int, int}', ], ], 'inlineCallableFunction' => [ ' $b ? 1 : 0; } $arr = [5, 4, 3, 1, 2]; usort($arr, "fooBar"); } }', ], 'closureSelf' => [ 'subitems = array_map( function(self $i): self { return $i; }, $in ); } } new A([new A, new A]);', ], 'arrayMapVariadicClosureArg' => [ ' [ ' [ ' $f($g($x)); }', 'assertions' => [], 'error_levels' => [], '7.4' ], 'returnsTypedClosureWithClasses' => [ ' [ '$a' => 'A', ], ], 'returnsTypedClosureWithSubclassParam' => [ ' [ '$a' => 'A', ], ], 'returnsTypedClosureWithParentReturn' => [ ' [ '$a' => 'A', ], ], 'inferArrayMapReturnTypeWithoutTypehints' => [ ' [], 'error_levels' => ['MissingClosureReturnType'], ], 'inferArrayMapReturnTypeWithTypehints' => [ ' [ 'invokable = $invokable; } public function callTheInvokableDirectly(): bool { return ($this->invokable)(); } public function callTheInvokableIndirectly(): bool { $r = $this->invokable; return $r(); } }', ], 'PHP71-mirrorCallableParams' => [ ' 0; }));', ], 'singleLineClosures' => [ ' [ '$a' => 'pure-Closure():pure-Closure():"hello"', '$b' => 'string', ], ], 'voidReturningArrayMap' => [ ' [ ' 0; } } acceptsIntToBool(Closure::fromCallable(new NamedInvokable));', ], 'PHP71-closureFromCallableInvokableAnonymousClass' => [ ' 0; } }; acceptsIntToBool(Closure::fromCallable($anonInvokable));', ], 'PHP71-publicCallableFromInside' => [ ' [ ' [ ' [ '$closure' => 'pure-Closure(string):(0|positive-int)', ] ], 'allowClosureWithNarrowerReturn' => [ ' [ ' [ ' [ ' [ ' [ ' [ 'a = fn(int $a) : int => $a + 5; } public function invoker(int $b) : int { return $this->a->__invoke($b); } }', 'assertions' => [], 'error_levels' => [], '7.4' ], 'annotateShortClosureReturn' => [ ' /** @var bool */ returnsBool();', [], [], '7.4' ], 'rememberParentAssertions' => [ 'a instanceof A) { function () use ($a): void { $a->a->foo(); }; } }' ], 'CallableWithArrayMap' => [ ' $className * @return callable(...mixed):T */ function maker(string $className) { return function(...$args) use ($className) { /** @psalm-suppress MixedMethodCall */ return new $className(...$args); }; } $maker = maker(stdClass::class); $result = array_map($maker, ["abc"]);', 'assertions' => [ '$result' => 'array{stdClass}' ], ], 'FirstClassCallable:NamedFunction:is_int' => [ ' [ '$closure' => 'pure-Closure(mixed):bool', '$result' => 'bool', ], [], '8.1' ], 'FirstClassCallable:NamedFunction:strlen' => [ ' [ '$closure' => 'pure-Closure(string):(0|positive-int)', '$result' => 'int|positive-int', ], [], '8.1' ], 'FirstClassCallable:InstanceMethod:UserDefined' => [ 'string); } } $test = new Test("test"); $closure = $test->length(...); $length = $closure(); ', 'assertions' => [ '$length' => 'int', ], [], '8.1' ], 'FirstClassCallable:InstanceMethod:Expr' => [ 'string); } } $test = new Test("test"); $method_name = "length"; $closure = $test->$method_name(...); $length = $closure(); ', 'assertions' => [ '$length' => 'int', ], [], '8.1' ], 'FirstClassCallable:InstanceMethod:BuiltIn' => [ 'count(...); $count = $closure(); ', 'assertions' => [ '$count' => 'int', ], [], '8.1' ], 'FirstClassCallable:StaticMethod' => [ ' [ '$length' => 'int', ], [], '8.1' ], 'FirstClassCallable:StaticMethod:Expr' => [ ' [ '$length' => 'int', ], [], '8.1' ], 'FirstClassCallable:InvokableObject' => [ ' [ '$length' => 'int', ], [], '8.1' ], 'FirstClassCallable:FromClosure' => [ ' strlen($string); $closure = $closure(...); ', 'assertions' => [ '$closure' => 'pure-Closure(string):(0|positive-int)', ], [], '8.1' ], 'FirstClassCallable:MagicInstanceMethod' => [ ' strlen($this->string), default => throw new \Error("Undefined method"), }; } } $test = new Test("test"); $closure = $test->length(...); $length = $closure(); ', 'assertions' => [ '$length' => 'int', ], [], '8.1' ], 'FirstClassCallable:MagicStaticMethod' => [ ' strlen((string) $args[0]), default => throw new \Error("Undefined method"), }; } } $closure = Test::length(...); $length = $closure("test"); ', 'assertions' => [ '$length' => 'int', ], [], '8.1' ], 'FirstClassCallable:WithArrayMap' => [ ' $value * $value; $result1 = array_map((new \SplQueue())->enqueue(...), $array); $result2 = array_map(strval(...), $array); $result3 = array_map($closure(...), $array); ', 'assertions' => [ '$result1' => 'array{null, null, null}', '$result2' => 'array{string, string, string}', '$result3' => 'array{int, int, int}', ], [], '8.1' ], 'FirstClassCallable:array_map' => [ ' [], [], '8.1', ], 'FirstClassCallable:AssignmentVisitorMap' => [ ' */ public array $handlers = []; public function register(): void { foreach ([1, 2, 3] as $index) { $this->push($this->handler(...)); } } /** * @param Closure():void $closure * @return void */ private function push(\Closure $closure): void { $this->handlers[] = $closure; } private function handler(): void { } } $test = new Test(); $test->register(); $handlers = $test->handlers; ', 'assertions' => [ '$handlers' => 'list', ], 'ignored_issues' => [], 'php_version' => '8.1', ], 'arrowFunctionReturnsNeverImplictly' => [ ' throw new Exception($a), $bar );', 'assertions' => [], 'error_levels' => [], '8.1' ], 'arrowFunctionReturnsNeverExplictly' => [ ' die(), $bar );', 'assertions' => [], 'error_levels' => [], '8.1' ], 'unknownFirstClassCallable' => [ ' */ public function providerInvalidCodeParse(): iterable { return [ 'wrongArg' => [ ' 'InvalidScalarArgument', ], 'noReturn' => [ ' 'InvalidReturnType', ], 'possiblyNullFunctionCall' => [ ' 'MixedReturnStatement', ], 'wrongParamType' => [ ' 'InvalidScalarArgument', ], 'missingClosureReturnType' => [ ' 'MissingClosureReturnType', ], 'returnsTypedClosureWithBadReturnType' => [ ' 'InvalidReturnStatement', ], 'returnsTypedCallableWithBadReturnType' => [ ' 'InvalidReturnStatement', ], 'returnsTypedClosureWithBadParamType' => [ ' 'InvalidReturnStatement', ], 'returnsTypedCallableWithBadParamType' => [ ' 'InvalidReturnStatement', ], 'returnsTypedClosureWithBadCall' => [ ' 'InvalidArgument', ], 'returnsTypedClosureWithSubclassParam' => [ ' 'LessSpecificReturnStatement', ], 'returnsTypedClosureWithSubclassReturn' => [ ' 'LessSpecificReturnStatement', ], 'returnsTypedClosureFromCallable' => [ ' 'LessSpecificReturnStatement', ], 'undefinedVariable' => [ ' 'UndefinedVariable', ], 'voidReturningArrayMap' => [ ' 'TypeDoesNotContainType', ], 'PHP71-closureFromCallableInvokableNamedClassWrongArgs' => [ ' 0; } } acceptsIntToBool(Closure::fromCallable(new NamedInvokable));', 'error_message' => 'InvalidScalarArgument', ], 'undefinedClassForCallable' => [ ' 'UndefinedClass', ], 'useDuplicateName' => [ ' 'DuplicateParam', ], 'PHP71-privateCallable' => [ ' 'InvalidArgument', ], 'prohibitCallableWithRequiredArg' => [ ' 'InvalidArgument', ], 'useClosureDocblockType' => [ ' 'ArgumentTypeCoercion - src' . DIRECTORY_SEPARATOR . 'somefile.php:13:28 - Argument 1 of takesB expects B, parent type A provided', ], 'closureByRefUseToMixed' => [ ' 'MixedReturnStatement' ], 'noCrashWhenComparingIllegitimateCallable' => [ ' ""; }', 'error_message' => 'InvalidReturnStatement', [], false, '7.4', ], 'detectImplicitVoidReturn' => [ 'getMessage(); } takesClosureReturningException( function () { echo "hello"; } );', 'error_message' => 'InvalidArgument' ], 'undefinedVariableInEncapsedString' => [ ' "$a"; ', 'error_message' => 'UndefinedVariable', [], false, '7.4' ], 'undefinedVariableInStringCast' => [ ' (string) $a; ', 'error_message' => 'UndefinedVariable', [], false, '7.4' ], 'forbidTemplateAnnotationOnClosure' => [ ' 'InvalidDocblock', ], 'forbidTemplateAnnotationOnShortClosure' => [ ' false; ', 'error_message' => 'InvalidDocblock', [], false, '7.4' ], 'closureInvalidArg' => [ ' 'InvalidArgument', ], 'FirstClassCallable:UndefinedMethod' => [ 'undefined(...); $count = $closure(); ', 'error_message' => 'UndefinedMethod', [], false, '8.1' ], 'FirstClassCallable:UndefinedMagicInstanceMethod' => [ ' throw new \Error("Undefined method"), }; } } $test = new Test(); $closure = $test->length(...); $length = $closure(); ', 'error_message' => 'UndefinedMagicMethod', [], false, '8.1' ], 'FirstClassCallable:UndefinedMagicStaticMethod' => [ ' throw new \Error("Undefined method"), }; } } $closure = Test::length(...); $length = $closure(); ', 'error_message' => 'MixedAssignment', [], false, '8.1', ], ]; } }