forked from vimeo/psalm
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
369 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
<?php | ||
|
||
use Psalm\Tests\Config\Plugin\Hook\PipeFunctionPlugin; | ||
|
||
/** | ||
* @template A | ||
* @template B | ||
* | ||
* @param callable(A): B $_ab | ||
* @return callable(list<A>): list<B> | ||
*/ | ||
function map(callable $_ab): callable | ||
{ | ||
throw new RuntimeException('???'); | ||
} | ||
|
||
/** | ||
* @template A | ||
* | ||
* @param callable(A): bool $_predicate | ||
* @return callable(list<A>): list<A> | ||
*/ | ||
function filter(callable $_predicate): callable | ||
{ | ||
throw new RuntimeException('???'); | ||
} | ||
|
||
/** | ||
* @return list<string> | ||
*/ | ||
function getList(): array | ||
{ | ||
return []; | ||
} | ||
|
||
function stringToInt(string $str): int | ||
{ | ||
return (int) $str; | ||
} | ||
|
||
/** | ||
* @psalm-immutable | ||
*/ | ||
final class Greater10 | ||
{ | ||
public int $val; | ||
|
||
public function __construct(int $val) | ||
{ | ||
$this->val = $val; | ||
} | ||
} | ||
|
||
/** | ||
* @template T1 | ||
* @template T2 | ||
* @template T3 | ||
* @template T4 | ||
* @template T5 | ||
* @template T6 | ||
* @template T7 | ||
* @template T8 | ||
* @template T9 | ||
* @template T10 | ||
* @template T11 | ||
* @template T12 | ||
* | ||
* @return mixed | ||
* @no-named-arguments | ||
* | ||
* @see PipeFunctionPlugin | ||
*/ | ||
function pipe(callable ...$_functions) | ||
{ | ||
throw new RuntimeException('???'); | ||
} | ||
|
||
/** @psalm-trace $_ */ | ||
$_ = pipe( | ||
getList(), | ||
map(fn($a) => stringToInt($a)), | ||
filter(fn($a) => $a > 10), | ||
map(fn($a) => new Greater10($a)) | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
<?xml version="1.0"?> | ||
<psalm | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xmlns="https://getpsalm.org/schema/config" | ||
name="Psalm for Psalm" | ||
useDocblockTypes="true" | ||
errorLevel="1" | ||
strictBinaryOperands="false" | ||
rememberPropertyAssignmentsAfterCall="true" | ||
checkForThrowsDocblock="false" | ||
throwExceptionOnError="0" | ||
findUnusedCode="true" | ||
ensureArrayStringOffsetsExist="true" | ||
ensureArrayIntOffsetsExist="true" | ||
resolveFromConfigFile="true" | ||
xsi:schemaLocation="https://getpsalm.org/schema/config config.xsd" | ||
limitMethodComplexity="true" | ||
errorBaseline="psalm-baseline.xml" | ||
findUnusedPsalmSuppress="true" | ||
> | ||
<stubs> | ||
<file name="stubs/phpparser.phpstub"/> | ||
</stubs> | ||
<projectFiles> | ||
<directory name="src"/> | ||
<directory name="tests"/> | ||
<directory name="examples"/> | ||
<file name="psalm"/> | ||
<file name="psalm-language-server"/> | ||
<file name="psalm-plugin"/> | ||
<file name="psalm-refactor"/> | ||
<file name="psalter"/> | ||
<ignoreFiles> | ||
<file name="src/Psalm/Internal/PhpTraverser/CustomTraverser.php"/> | ||
<file name="tests/ErrorBaselineTest.php"/> | ||
<file name="vendor/symfony/console/Command/Command.php"/> | ||
<directory name="tests/fixtures"/> | ||
<file name="vendor/felixfbecker/advanced-json-rpc/lib/Dispatcher.php" /> | ||
<directory name="vendor/netresearch/jsonmapper" /> | ||
<directory name="vendor/phpunit" /> | ||
</ignoreFiles> | ||
</projectFiles> | ||
|
||
<ignoreExceptions> | ||
<class name="UnexpectedValueException"/> | ||
<class name="InvalidArgumentException"/> | ||
<class name="LogicException"/> | ||
</ignoreExceptions> | ||
|
||
<plugins> | ||
<plugin filename="examples/plugins/FunctionCasingChecker.php"/> | ||
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/> | ||
<plugin filename="examples/plugins/InternalChecker.php"/> | ||
<pluginClass class="Psalm\Tests\Config\Plugin\Hook\PipeFunctionPlugin"/> | ||
</plugins> | ||
|
||
<issueHandlers> | ||
<PossiblyNullOperand errorLevel="suppress"/> | ||
|
||
<DeprecatedMethod> | ||
<errorLevel type="suppress"> | ||
<directory name="tests" /> | ||
</errorLevel> | ||
</DeprecatedMethod> | ||
|
||
<DeprecatedClass> | ||
<errorLevel type="suppress"> | ||
<referencedClass name="PackageVersions\Versions"/> | ||
</errorLevel> | ||
</DeprecatedClass> | ||
|
||
<UnusedParam> | ||
<errorLevel type="suppress"> | ||
<directory name="examples"/> | ||
</errorLevel> | ||
</UnusedParam> | ||
|
||
<PossiblyUnusedParam> | ||
<errorLevel type="suppress"> | ||
<directory name="examples"/> | ||
</errorLevel> | ||
</PossiblyUnusedParam> | ||
|
||
<UnusedClass> | ||
<errorLevel type="suppress"> | ||
<directory name="examples"/> | ||
<directory name="src/Psalm/Internal/Fork" /> | ||
<directory name="src/Psalm/Node" /> | ||
<file name="src/Psalm/Plugin/Shepherd.php" /> | ||
</errorLevel> | ||
</UnusedClass> | ||
|
||
<MissingConstructor> | ||
<errorLevel type="suppress"> | ||
<directory name="tests"/> | ||
</errorLevel> | ||
</MissingConstructor> | ||
|
||
<PossiblyUndefinedIntArrayOffset> | ||
<errorLevel type="suppress"> | ||
<directory name="src/Psalm/Internal/ExecutionEnvironment" /> | ||
<directory name="tests"/> | ||
</errorLevel> | ||
</PossiblyUndefinedIntArrayOffset> | ||
|
||
<MissingThrowsDocblock errorLevel="info"/> | ||
|
||
<PossiblyUnusedProperty> | ||
<errorLevel type="suppress"> | ||
<file name="src/Psalm/Report.php"/> | ||
</errorLevel> | ||
</PossiblyUnusedProperty> | ||
|
||
<PossiblyUnusedMethod> | ||
<errorLevel type="suppress"> | ||
<directory name="src/Psalm/Plugin"/> | ||
<directory name="src/Psalm/SourceControl/Git/"/> | ||
<file name="src/Psalm/Internal/LanguageServer/Client/TextDocument.php"/> | ||
<file name="src/Psalm/Internal/LanguageServer/Server/TextDocument.php"/> | ||
<referencedMethod name="Psalm\Codebase::getParentInterfaces"/> | ||
<referencedMethod name="Psalm\Codebase::getMethodParams"/> | ||
<referencedMethod name="Psalm\Codebase::getMethodReturnType"/> | ||
<referencedMethod name="Psalm\Codebase::getMethodReturnTypeLocation"/> | ||
<referencedMethod name="Psalm\Codebase::getDeclaringMethodId"/> | ||
<referencedMethod name="Psalm\Codebase::getAppearingMethodId"/> | ||
<referencedMethod name="Psalm\Codebase::getOverriddenMethodIds"/> | ||
<referencedMethod name="Psalm\Codebase::getCasedMethodId"/> | ||
<referencedMethod name="Psalm\Codebase::createClassLikeStorage"/> | ||
<referencedMethod name="Psalm\Codebase::isVariadic"/> | ||
<referencedMethod name="Psalm\Codebase::getMethodReturnsByRef"/> | ||
</errorLevel> | ||
</PossiblyUnusedMethod> | ||
|
||
<InternalMethod> | ||
<errorLevel type="suppress"> | ||
<directory name="tests"/> | ||
</errorLevel> | ||
</InternalMethod> | ||
|
||
<PossiblyUndefinedStringArrayOffset> | ||
<errorLevel type="suppress"> | ||
<directory name="src/Psalm/Internal/Provider/ReturnTypeProvider" /> | ||
<file name="src/Psalm/Internal/Type/AssertionReconciler.php" /> | ||
<file name="src/Psalm/Internal/Type/NegatedAssertionReconciler.php" /> | ||
<file name="src/Psalm/Internal/Type/SimpleAssertionReconciler.php" /> | ||
<file name="src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php" /> | ||
<directory name="tests"/> | ||
</errorLevel> | ||
</PossiblyUndefinedStringArrayOffset> | ||
|
||
<MixedPropertyTypeCoercion> | ||
<errorLevel type="suppress"> | ||
<directory name="vendor/nikic/php-parser" /> | ||
</errorLevel> | ||
</MixedPropertyTypeCoercion> | ||
|
||
<PropertyTypeCoercion> | ||
<errorLevel type="suppress"> | ||
<directory name="vendor/nikic/php-parser" /> | ||
</errorLevel> | ||
</PropertyTypeCoercion> | ||
|
||
<MixedAssignment> | ||
<errorLevel type="suppress"> | ||
<directory name="vendor/nikic/php-parser" /> | ||
</errorLevel> | ||
</MixedAssignment> | ||
</issueHandlers> | ||
</psalm> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psalm\Tests\Config\Plugin\Hook; | ||
|
||
use PhpParser\Node\Arg; | ||
use Psalm\Plugin\EventHandler\Event\FunctionParamsProviderEvent; | ||
use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent; | ||
use Psalm\Plugin\EventHandler\FunctionParamsProviderInterface; | ||
use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface; | ||
use Psalm\Plugin\PluginEntryPointInterface; | ||
use Psalm\Plugin\RegistrationInterface; | ||
use Psalm\Storage\FunctionLikeParameter; | ||
use Psalm\Type; | ||
use Psalm\Type\Atomic\TTemplateParam; | ||
use Psalm\Type\Union; | ||
use SimpleXMLElement; | ||
|
||
use function array_map; | ||
use function count; | ||
use function range; | ||
|
||
final class PipeFunctionPlugin implements FunctionParamsProviderInterface, FunctionReturnTypeProviderInterface, PluginEntryPointInterface | ||
{ | ||
public function __invoke(RegistrationInterface $registration, ?SimpleXMLElement $config = null): void | ||
{ | ||
$registration->registerHooksFromClass(self::class); | ||
} | ||
|
||
/** | ||
* @return array<lowercase-string> | ||
*/ | ||
public static function getFunctionIds(): array | ||
{ | ||
return ['pipe']; | ||
} | ||
|
||
/** | ||
* @return ?array<int, FunctionLikeParameter> | ||
*/ | ||
public static function getFunctionParams(FunctionParamsProviderEvent $event): ?array | ||
{ | ||
$args_count = count($event->getCallArgs()); | ||
|
||
if ($args_count < 2 || $args_count > 12) { | ||
return null; | ||
} | ||
|
||
return [ | ||
// First input arg to pipe. | ||
new FunctionLikeParameter('in', false, self::createTemplate()), | ||
// Rest callable args. | ||
// A -> B | ||
// B -> C | ||
// C -> D and etc... | ||
...array_map( | ||
fn(int $arg_offset) => new FunctionLikeParameter( | ||
'fn' . $arg_offset, | ||
false, | ||
new Union([ | ||
new Type\Atomic\TCallable( | ||
'callable', | ||
[new FunctionLikeParameter('a', false, self::createTemplate($arg_offset - 1))], | ||
self::createTemplate($arg_offset), | ||
), | ||
]) | ||
), | ||
range(2, $args_count), | ||
), | ||
]; | ||
} | ||
|
||
private static function createTemplate(int $offset = 1): Union | ||
{ | ||
return new Union([ | ||
new TTemplateParam('T' . $offset, Type::getMixed(), 'fn-pipe') | ||
]); | ||
} | ||
|
||
public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): ?Union | ||
{ | ||
$source = $event->getStatementsSource(); | ||
$type_provider = $source->getNodeTypeProvider(); | ||
|
||
$args = $event->getStmt()->args; | ||
$args_count = count($args); | ||
|
||
if ($args_count < 2 || $args_count > 12) { | ||
return null; | ||
} | ||
|
||
// Try to fetch return type from last callable arg. | ||
$last_callable_arg = $args[$args_count - 1]; | ||
|
||
if (!($last_callable_arg instanceof Arg) || | ||
!($type = $type_provider->getType($last_callable_arg->value)) || | ||
!($type->isSingle()) | ||
) { | ||
return null; | ||
} | ||
|
||
$atomic = $type->getSingleAtomic(); | ||
|
||
if (!$atomic instanceof Type\Atomic\TCallable && !$atomic instanceof Type\Atomic\TClosure) { | ||
return null; | ||
} | ||
|
||
return $atomic->return_type; | ||
} | ||
} |