diff --git a/tests/Config/Plugin/FunctionPlugin.php b/tests/Config/Plugin/FunctionPlugin.php index b17575bda08..fb27dc6910a 100644 --- a/tests/Config/Plugin/FunctionPlugin.php +++ b/tests/Config/Plugin/FunctionPlugin.php @@ -4,6 +4,7 @@ use Psalm\Plugin\PluginEntryPointInterface; use Psalm\Plugin\RegistrationInterface; +use Psalm\Test\Config\Plugin\Hook\CallUserFuncLikeParamsProvider; use Psalm\Test\Config\Plugin\Hook\MagicFunctionProvider; use SimpleXMLElement; @@ -12,8 +13,10 @@ class FunctionPlugin implements PluginEntryPointInterface { public function __invoke(RegistrationInterface $registration, ?SimpleXMLElement $config = null): void { + require_once __DIR__ . '/Hook/CallUserFuncLikeParamsProvider.php'; require_once __DIR__ . '/Hook/MagicFunctionProvider.php'; + $registration->registerHooksFromClass(CallUserFuncLikeParamsProvider::class); $registration->registerHooksFromClass(MagicFunctionProvider::class); } } diff --git a/tests/Config/Plugin/Hook/CallUserFuncLikeParamsProvider.php b/tests/Config/Plugin/Hook/CallUserFuncLikeParamsProvider.php new file mode 100644 index 00000000000..e89ffc17ca4 --- /dev/null +++ b/tests/Config/Plugin/Hook/CallUserFuncLikeParamsProvider.php @@ -0,0 +1,85 @@ + + */ + public static function getFunctionIds(): array + { + return ['call_user_func_like']; + } + + /** + * @return ?array + */ + public static function getFunctionParams(FunctionParamsProviderEvent $event): ?array + { + $statements_source = $event->getStatementsSource(); + if (!$statements_source instanceof StatementsAnalyzer) { + return null; + } + + $call_args = $event->getCallArgs(); + if (!isset($call_args[0])) { + return null; + } + + $function_call_arg = $call_args[0]; + + $mapping_function_ids = array(); + if ($function_call_arg->value instanceof PhpParser\Node\Scalar\String_ + || $function_call_arg->value instanceof PhpParser\Node\Expr\Array_ + || $function_call_arg->value instanceof PhpParser\Node\Expr\BinaryOp\Concat + ) { + $mapping_function_ids = CallAnalyzer::getFunctionIdsFromCallableArg( + $statements_source, + $function_call_arg->value, + ); + } + + if (!isset($mapping_function_ids[0])) { + return null; + } + + $codebase = $event->getStatementsSource()->getCodebase(); + $function_like_storage = $codebase->getFunctionLikeStorage($statements_source, $mapping_function_ids[0]); + + $callback_param_types = []; + foreach ($function_like_storage->params as $function_like_parameter) { + $param_type_union = $function_like_parameter->type; + if (!$param_type_union) { + $param_type_union = Type::getMixed(); + } + + if ($function_like_parameter->is_nullable || $function_like_parameter->is_optional) { + $param_type_union = $param_type_union->setPossiblyUndefined(true); + } + + $callback_param_types[] = $param_type_union; + } + + $callback_params = new TKeyedArray( + $callback_param_types, + ); + + return array( + new FunctionLikeParameter('callable', false, new Union([new TCallable()]), null, null, null, false), + new FunctionLikeParameter('params', false, new Union([$callback_params]), null, null, null, false), + ); + } +} diff --git a/tests/Config/PluginTest.php b/tests/Config/PluginTest.php index bfd98cffc0b..fb49a0c20e2 100644 --- a/tests/Config/PluginTest.php +++ b/tests/Config/PluginTest.php @@ -767,6 +767,57 @@ public function testFunctionProviderHooksInvalidArg(): void $this->analyzeFile($file_path, new Context()); } + public function testFunctionProviderHooksThisClassInvalidArg(): void + { + $this->expectExceptionMessage('InvalidScalarArgument'); + $this->expectException(CodeException::class); + require_once __DIR__ . '/Plugin/FunctionPlugin.php'; + + $this->project_analyzer = $this->getProjectAnalyzerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, + ' + + + + + + + + ', + ), + ); + + $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'analyzeFile($file_path, new Context()); + } + public function testAfterAnalysisHooks(): void { require_once __DIR__ . '/Plugin/AfterAnalysisPlugin.php';