1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-13 17:57:37 +01:00
psalm/src/Psalm/Checker/FunctionChecker.php

803 lines
27 KiB
PHP
Raw Normal View History

2016-01-08 00:28:27 +01:00
<?php
namespace Psalm\Checker;
2016-01-08 00:28:27 +01:00
2016-02-04 15:22:46 +01:00
use PhpParser;
use Psalm\CodeLocation;
use Psalm\FunctionLikeParameter;
2016-10-14 06:53:43 +02:00
use Psalm\Issue\InvalidReturnType;
2016-11-02 07:29:00 +01:00
use Psalm\IssueBuffer;
use Psalm\StatementsSource;
use Psalm\Storage\FunctionLikeStorage;
2016-08-14 05:26:45 +02:00
use Psalm\Type;
2016-01-08 00:28:27 +01:00
2016-08-14 05:26:45 +02:00
class FunctionChecker extends FunctionLikeChecker
2016-01-08 00:28:27 +01:00
{
2016-11-01 05:39:41 +01:00
/**
* @var array<array<string,string>>|null
2016-11-01 05:39:41 +01:00
*/
protected static $call_map = null;
2016-11-01 05:39:41 +01:00
/**
* @var array<string, FunctionLikeStorage>
2016-11-01 05:39:41 +01:00
*/
2016-08-14 05:26:45 +02:00
protected static $builtin_functions = [];
2016-11-01 05:39:41 +01:00
/**
* @var array<string, FunctionLikeStorage>
*/
public static $stubbed_functions = [];
2016-06-16 02:16:40 +02:00
/**
2016-11-05 02:14:04 +01:00
* @param mixed $function
2016-08-14 05:26:45 +02:00
* @param StatementsSource $source
2016-06-16 02:16:40 +02:00
*/
public function __construct($function, StatementsSource $source)
2016-05-16 22:12:02 +02:00
{
2016-10-23 18:24:53 +02:00
if (!$function instanceof PhpParser\Node\Stmt\Function_) {
2016-10-23 07:57:11 +02:00
throw new \InvalidArgumentException('Bad');
}
2016-08-14 05:26:45 +02:00
parent::__construct($function, $source);
2016-01-08 00:28:27 +01:00
}
2016-10-14 06:53:43 +02:00
/**
* @param string $function_id
2017-05-27 02:16:18 +02:00
*
2017-05-25 04:07:49 +02:00
* @return bool
2016-10-14 06:53:43 +02:00
*/
public static function functionExists(StatementsChecker $statements_checker, $function_id)
{
$project_checker = $statements_checker->getFileChecker()->project_checker;
$file_storage = $project_checker->file_storage_provider->get($statements_checker->getFilePath());
if (isset($file_storage->declaring_function_ids[$function_id])) {
2016-08-14 05:26:45 +02:00
return true;
}
2016-08-14 05:26:45 +02:00
if (strpos($function_id, '::') !== false) {
2016-08-15 19:37:21 +02:00
$function_id = strtolower(preg_replace('/^[^:]+::/', '', $function_id));
2016-08-14 05:26:45 +02:00
}
if (isset(self::$builtin_functions[$function_id])) {
return true;
2016-08-14 05:26:45 +02:00
}
if (isset(self::$stubbed_functions[$function_id])) {
return true;
}
if (isset($statements_checker->getFunctionCheckers()[$function_id])) {
return true;
}
if (self::extractReflectionInfo($function_id) === false) {
return false;
}
return true;
}
2016-08-11 01:21:03 +02:00
2016-10-15 06:12:57 +02:00
/**
* @param string $function_id
2017-05-27 02:16:18 +02:00
*
2017-02-10 02:35:17 +01:00
* @return FunctionLikeStorage
2016-10-15 06:12:57 +02:00
*/
public static function getStorage(StatementsChecker $statements_checker, $function_id)
2016-08-11 01:21:03 +02:00
{
if (isset(self::$stubbed_functions[$function_id])) {
return self::$stubbed_functions[$function_id];
}
if (isset(self::$builtin_functions[$function_id])) {
2017-02-10 02:35:17 +01:00
return self::$builtin_functions[$function_id];
2016-08-11 01:21:03 +02:00
}
$project_checker = $statements_checker->getFileChecker()->project_checker;
$file_path = $statements_checker->getFilePath();
$file_storage = $project_checker->file_storage_provider->get($file_path);
$function_checkers = $statements_checker->getFunctionCheckers();
if (isset($function_checkers[$function_id])) {
$function_id = $function_checkers[$function_id]->getMethodId();
if (!isset($file_storage->functions[$function_id])) {
throw new \UnexpectedValueException(
'Expecting ' . $function_id . ' to have storage in ' . $file_path
);
}
return $file_storage->functions[$function_id];
}
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
if (!isset($file_storage->declaring_function_ids[$function_id])) {
throw new \UnexpectedValueException(
'Expecting ' . $function_id . ' to have storage in ' . $file_path
);
}
$declaring_file_path = $file_storage->declaring_function_ids[$function_id];
$declaring_file_storage = $project_checker->file_storage_provider->get($declaring_file_path);
if (!isset($declaring_file_storage->functions[$function_id])) {
throw new \UnexpectedValueException(
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
'Not expecting ' . $function_id . ' to not have storage in ' . $declaring_file_path
);
}
return $declaring_file_storage->functions[$function_id];
2016-08-11 01:21:03 +02:00
}
/**
* @param string $function_id
* @param string $file_path
2017-05-27 02:16:18 +02:00
*
2017-05-25 04:07:49 +02:00
* @return bool
*/
public static function isVariadic(ProjectChecker $project_checker, $function_id, $file_path)
{
$file_storage = $project_checker->file_storage_provider->get($file_path);
return isset($file_storage->functions[$function_id]) && $file_storage->functions[$function_id]->variadic;
}
2016-10-14 06:53:43 +02:00
/**
* @param string $function_id
2017-05-27 02:16:18 +02:00
*
* @return false|null
2016-10-14 06:53:43 +02:00
*/
2016-08-14 05:26:45 +02:00
protected static function extractReflectionInfo($function_id)
2016-08-11 01:21:03 +02:00
{
2016-08-14 05:26:45 +02:00
try {
$reflection_function = new \ReflectionFunction($function_id);
2016-08-11 01:21:03 +02:00
$storage = self::$builtin_functions[$function_id] = new FunctionLikeStorage();
2016-08-11 01:21:03 +02:00
$reflection_params = $reflection_function->getParameters();
2016-08-11 01:21:03 +02:00
2016-10-14 06:53:43 +02:00
/** @var \ReflectionParameter $param */
2016-08-14 05:26:45 +02:00
foreach ($reflection_params as $param) {
$param_obj = self::getReflectionParamData($param);
$storage->params[] = $param_obj;
2016-08-11 01:21:03 +02:00
}
$storage->cased_name = $reflection_function->getName();
$config = \Psalm\Config::getInstance();
if (version_compare(PHP_VERSION, '7.0.0dev', '>=')
&& $reflection_return_type = $reflection_function->getReturnType()
) {
$storage->return_type = Type::parseString((string)$reflection_return_type);
}
if ($reflection_function->isUserDefined()) {
$docblock_info = null;
$doc_comment = $reflection_function->getDocComment();
if (!$doc_comment) {
return;
}
try {
$docblock_info = CommentChecker::extractFunctionDocblockInfo(
(string)$doc_comment,
0
);
2017-07-10 05:26:16 +02:00
} catch (\Psalm\Exception\DocblockParseException $e) {
// do nothing
}
if (!$docblock_info) {
return;
}
if ($docblock_info->deprecated) {
$storage->deprecated = true;
}
if ($docblock_info->variadic) {
$storage->variadic = true;
}
if ($docblock_info->ignore_nullable_return && $storage->return_type) {
$storage->return_type->ignore_nullable_issues = true;
}
$storage->suppressed_issues = $docblock_info->suppress;
if (!$config->use_docblock_types) {
2017-07-10 05:26:16 +02:00
return;
}
if ($docblock_info->return_type) {
if (!$storage->return_type) {
$storage->return_type = Type::parseString($docblock_info->return_type);
$storage->return_type->setFromDocblock();
if ($docblock_info->ignore_nullable_return) {
$storage->return_type->ignore_nullable_issues = true;
}
}
}
}
2016-11-02 07:29:00 +01:00
} catch (\ReflectionException $e) {
return false;
2016-08-11 01:21:03 +02:00
}
}
/**
* @param Type\Union $return_type
* @param array<string, string> $template_types
2017-05-27 02:16:18 +02:00
*
* @return Type\Union
*/
public static function replaceTemplateTypes(Type\Union $return_type, array $template_types)
{
$ignore_nullable_issues = $return_type->ignore_nullable_issues;
$type_tokens = Type::tokenize((string)$return_type);
foreach ($type_tokens as &$type_token) {
if (isset($template_types[$type_token])) {
$type_token = $template_types[$type_token];
}
}
$result_type = Type::parseString(implode('', $type_tokens));
$result_type->ignore_nullable_issues = $ignore_nullable_issues;
return $result_type;
}
2016-08-22 21:00:12 +02:00
/**
* @param string $function_id
2017-05-27 02:16:18 +02:00
*
2016-11-21 05:57:37 +01:00
* @return array|null
* @psalm-return array<int, array<int, FunctionLikeParameter>>|null
2016-08-22 21:00:12 +02:00
*/
public static function getParamsFromCallMap($function_id)
{
$call_map = self::getCallMap();
$call_map_key = strtolower($function_id);
if (!isset($call_map[$call_map_key])) {
return null;
}
$call_map_functions = [];
$call_map_functions[] = $call_map[$call_map_key];
2017-05-27 02:05:57 +02:00
for ($i = 1; $i < 10; ++$i) {
2016-11-02 07:29:00 +01:00
if (!isset($call_map[$call_map_key . '\'' . $i])) {
2016-08-22 21:00:12 +02:00
break;
}
2016-11-02 07:29:00 +01:00
$call_map_functions[] = $call_map[$call_map_key . '\'' . $i];
2016-08-22 21:00:12 +02:00
}
$function_type_options = [];
foreach ($call_map_functions as $call_map_function_args) {
array_shift($call_map_function_args);
$function_types = [];
foreach ($call_map_function_args as $arg_name => $arg_type) {
$by_reference = false;
2016-10-26 17:51:59 +02:00
$optional = false;
$variadic = false;
2016-08-22 21:00:12 +02:00
if ($arg_name[0] === '&') {
$arg_name = substr($arg_name, 1);
$by_reference = true;
}
2016-10-26 17:51:59 +02:00
if (substr($arg_name, -1) === '=') {
$arg_name = substr($arg_name, 0, -1);
$optional = true;
}
if (substr($arg_name, 0, 3) === '...') {
$arg_name = substr($arg_name, 3);
$variadic = true;
}
$param_type = $arg_type
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
? Type::parseString($arg_type)
: Type::getMixed();
$function_types[] = new FunctionLikeParameter(
$arg_name,
$by_reference,
$param_type,
null,
2016-10-30 17:46:18 +01:00
$optional,
false,
$variadic
);
2016-08-22 21:00:12 +02:00
}
$function_type_options[] = $function_types;
}
return $function_type_options;
}
2016-10-14 06:53:43 +02:00
/**
2016-11-01 05:39:41 +01:00
* @param string $function_id
2017-05-27 02:16:18 +02:00
*
2016-10-14 06:53:43 +02:00
* @return Type\Union
*/
2016-11-01 05:39:41 +01:00
public static function getReturnTypeFromCallMap($function_id)
{
2016-08-22 21:00:12 +02:00
$call_map_key = strtolower($function_id);
2016-11-01 05:39:41 +01:00
$call_map = self::getCallMap();
2016-08-22 21:00:12 +02:00
2016-11-01 05:39:41 +01:00
if (!isset($call_map[$call_map_key])) {
throw new \InvalidArgumentException('Function ' . $function_id . ' was not found in callmap');
}
2016-08-22 21:00:12 +02:00
2016-11-01 05:39:41 +01:00
if (!$call_map[$call_map_key][0]) {
return Type::getMixed();
2016-08-22 21:00:12 +02:00
}
2016-11-01 05:39:41 +01:00
return Type::parseString($call_map[$call_map_key][0]);
}
2016-08-22 21:00:12 +02:00
2016-11-01 05:39:41 +01:00
/**
* @param string $function_id
* @param array<PhpParser\Node\Arg> $call_args
* @param CodeLocation $code_location
2016-11-01 05:39:41 +01:00
* @param array $suppressed_issues
2017-05-27 02:16:18 +02:00
*
2016-11-01 05:39:41 +01:00
* @return Type\Union
*/
public static function getReturnTypeFromCallMapWithArgs(
StatementsChecker $statements_checker,
2016-11-01 05:39:41 +01:00
$function_id,
array $call_args,
CodeLocation $code_location,
2016-11-01 05:39:41 +01:00
array $suppressed_issues
) {
$call_map_key = strtolower($function_id);
2016-08-22 21:00:12 +02:00
2016-08-30 06:05:13 +02:00
$call_map = self::getCallMap();
2016-10-22 23:35:59 +02:00
if (!isset($call_map[$call_map_key])) {
throw new \InvalidArgumentException('Function ' . $function_id . ' was not found in callmap');
}
2016-11-01 05:39:41 +01:00
if ($call_args) {
2017-05-27 02:05:57 +02:00
if (in_array($call_map_key, ['str_replace', 'preg_replace', 'preg_replace_callback'], true)) {
2016-11-01 05:39:41 +01:00
if (isset($call_args[2]->value->inferredType)) {
/** @var Type\Union */
$subject_type = $call_args[2]->value->inferredType;
if (!$subject_type->hasString() && $subject_type->hasArray()) {
return Type::getArray();
}
return Type::getString();
}
}
2017-05-27 02:05:57 +02:00
if (in_array($call_map_key, ['pathinfo'], true)) {
2016-11-01 05:39:41 +01:00
if (isset($call_args[1])) {
return Type::getString();
}
return Type::getArray();
}
2016-11-02 14:24:36 +01:00
if (substr($call_map_key, 0, 6) === 'array_') {
2016-11-04 01:51:56 +01:00
$array_return_type = self::getArrayReturnType(
$statements_checker,
2016-11-04 01:51:56 +01:00
$call_map_key,
$call_args,
$code_location,
2016-11-04 01:51:56 +01:00
$suppressed_issues
);
2016-11-02 14:24:36 +01:00
if ($array_return_type) {
return $array_return_type;
2016-08-30 06:05:13 +02:00
}
2016-11-02 14:24:36 +01:00
}
2016-08-30 06:05:13 +02:00
2016-11-02 14:24:36 +01:00
if ($call_map_key === 'explode' || $call_map_key === 'preg_split') {
return Type::parseString('array<int, string>');
}
if ($call_map_key === 'min' || $call_map_key === 'max') {
if (isset($call_args[0])) {
$first_arg = $call_args[0]->value;
2016-12-31 02:05:32 +01:00
if (isset($first_arg->inferredType)) {
if ($first_arg->inferredType->hasArray()) {
$array_type = $first_arg->inferredType->types['array'];
if ($array_type instanceof Type\Atomic\ObjectLike) {
return $array_type->getGenericTypeParam();
}
if ($array_type instanceof Type\Atomic\TArray) {
return clone $array_type->type_params[1];
}
} elseif ($first_arg->inferredType->hasScalarType() &&
($second_arg = $call_args[1]->value) &&
2016-12-31 02:39:12 +01:00
isset($second_arg->inferredType) &&
$second_arg->inferredType->hasScalarType()
) {
return Type::combineUnionTypes($first_arg->inferredType, $second_arg->inferredType);
}
}
}
}
2016-11-02 14:24:36 +01:00
}
2016-11-02 14:24:36 +01:00
if (!$call_map[$call_map_key][0]) {
return Type::getMixed();
}
2016-11-02 14:24:36 +01:00
return Type::parseString($call_map[$call_map_key][0]);
}
2016-11-02 14:24:36 +01:00
/**
* @param string $call_map_key
* @param array<PhpParser\Node\Arg> $call_args
* @param CodeLocation $code_location
2016-11-02 14:24:36 +01:00
* @param array $suppressed_issues
2017-05-27 02:16:18 +02:00
*
2016-11-02 14:24:36 +01:00
* @return Type\Union|null
*/
2016-11-04 01:51:56 +01:00
protected static function getArrayReturnType(
StatementsChecker $statements_checker,
2016-11-04 01:51:56 +01:00
$call_map_key,
$call_args,
CodeLocation $code_location,
2016-11-04 01:51:56 +01:00
array $suppressed_issues
) {
2016-12-25 02:33:14 +01:00
if ($call_map_key === 'array_map') {
return self::getArrayMapReturnType(
$statements_checker,
$call_map_key,
$call_args,
$code_location,
$suppressed_issues
);
2016-11-02 14:24:36 +01:00
}
2016-08-22 21:00:12 +02:00
2016-11-02 14:24:36 +01:00
$first_arg = isset($call_args[0]->value) ? $call_args[0]->value : null;
$second_arg = isset($call_args[1]->value) ? $call_args[1]->value : null;
2016-10-14 06:53:43 +02:00
2016-11-02 14:24:36 +01:00
if ($call_map_key === 'array_merge') {
$inner_value_types = [];
$inner_key_types = [];
2016-08-22 21:00:12 +02:00
2016-11-02 14:24:36 +01:00
foreach ($call_args as $offset => $call_arg) {
if (!isset($call_arg->value->inferredType)) {
return Type::getArray();
}
2016-08-22 21:00:12 +02:00
2016-11-02 14:24:36 +01:00
foreach ($call_arg->value->inferredType->types as $type_part) {
if (!$type_part instanceof Type\Atomic\TArray) {
if ($type_part instanceof Type\Atomic\ObjectLike) {
$type_part = new Type\Atomic\TArray([
Type::getString(),
2017-05-27 02:05:57 +02:00
$type_part->getGenericTypeParam(),
]);
} else {
return Type::getArray();
}
2016-08-22 21:00:12 +02:00
}
2016-11-02 14:24:36 +01:00
if ($type_part->type_params[1]->isEmpty()) {
continue;
2016-08-22 21:00:12 +02:00
}
2016-11-02 14:24:36 +01:00
$inner_key_types = array_merge(array_values($type_part->type_params[0]->types), $inner_key_types);
2016-11-04 01:51:56 +01:00
$inner_value_types = array_merge(
array_values($type_part->type_params[1]->types),
$inner_value_types
);
2016-08-22 21:00:12 +02:00
}
}
2016-08-22 21:00:12 +02:00
if ($inner_value_types) {
return new Type\Union([
new Type\Atomic\TArray([
Type::combineTypes($inner_key_types),
2017-05-27 02:05:57 +02:00
Type::combineTypes($inner_value_types),
]),
]);
2016-11-02 14:24:36 +01:00
}
return Type::getArray();
}
2016-12-25 02:33:14 +01:00
if ($call_map_key === 'array_filter') {
$first_arg_array = $first_arg
2017-02-11 01:10:13 +01:00
&& isset($first_arg->inferredType)
&& isset($first_arg->inferredType->types['array'])
&& ($first_arg->inferredType->types['array'] instanceof Type\Atomic\TArray ||
$first_arg->inferredType->types['array'] instanceof Type\Atomic\ObjectLike)
2017-02-11 01:10:13 +01:00
? $first_arg->inferredType->types['array']
: null;
if (!$first_arg_array) {
2016-12-25 02:33:14 +01:00
return Type::getArray();
}
$second_arg = isset($call_args[1]->value) ? $call_args[1]->value : null;
if ($first_arg_array instanceof Type\Atomic\TArray) {
$inner_type = $first_arg_array->type_params[1];
$key_type = clone $first_arg_array->type_params[0];
} else {
2017-05-25 04:07:49 +02:00
$inner_type = $first_arg_array->getGenericTypeParam();
$key_type = Type::getString();
}
if (!$second_arg) {
$inner_type->removeType('null');
$inner_type->removeType('false');
}
2016-12-25 02:34:47 +01:00
return new Type\Union([
new Type\Atomic\TArray([
$key_type,
2017-05-27 02:05:57 +02:00
$inner_type,
]),
2016-12-25 02:34:47 +01:00
]);
2016-12-25 02:33:14 +01:00
}
2016-11-04 01:51:56 +01:00
return null;
2016-11-02 14:24:36 +01:00
}
/**
* @param string $call_map_key
* @param array<PhpParser\Node\Arg> $call_args
* @param CodeLocation $code_location
2016-11-02 14:24:36 +01:00
* @param array $suppressed_issues
2017-05-27 02:16:18 +02:00
*
2016-11-02 14:24:36 +01:00
* @return Type\Union
*/
2016-11-04 01:51:56 +01:00
protected static function getArrayMapReturnType(
StatementsChecker $statements_checker,
2016-11-04 01:51:56 +01:00
$call_map_key,
$call_args,
CodeLocation $code_location,
2016-11-04 01:51:56 +01:00
array $suppressed_issues
) {
2016-11-02 14:24:36 +01:00
$function_index = $call_map_key === 'array_map' ? 0 : 1;
$array_index = $call_map_key === 'array_map' ? 1 : 0;
$array_arg = isset($call_args[$array_index]->value) ? $call_args[$array_index]->value : null;
$array_arg_type = $array_arg
2016-11-02 07:29:00 +01:00
&& isset($array_arg->inferredType)
&& isset($array_arg->inferredType->types['array'])
&& $array_arg->inferredType->types['array'] instanceof Type\Atomic\TArray
2016-11-02 07:29:00 +01:00
? $array_arg->inferredType->types['array']
: null;
2016-11-02 14:24:36 +01:00
if (isset($call_args[$function_index])) {
$function_call_arg = $call_args[$function_index];
2016-12-07 20:13:39 +01:00
if ($function_call_arg->value instanceof PhpParser\Node\Expr\Closure &&
isset($function_call_arg->value->inferredType) &&
$function_call_arg->value->inferredType->types['Closure'] instanceof Type\Atomic\Fn
2016-12-07 20:13:39 +01:00
) {
$closure_return_type = $function_call_arg->value->inferredType->types['Closure']->return_type;
2016-11-02 14:24:36 +01:00
2016-12-07 20:13:39 +01:00
if ($closure_return_type->isVoid()) {
2016-11-02 14:24:36 +01:00
IssueBuffer::accepts(
new InvalidReturnType(
'No return type could be found in the closure passed to ' . $call_map_key,
$code_location
2016-11-02 14:24:36 +01:00
),
$suppressed_issues
);
2016-10-22 23:35:59 +02:00
return Type::getArray();
}
$key_type = $array_arg_type ? clone $array_arg_type->type_params[0] : Type::getMixed();
2016-11-02 14:24:36 +01:00
if ($call_map_key === 'array_map') {
2016-12-07 20:13:39 +01:00
$inner_type = clone $closure_return_type;
2017-05-25 04:07:49 +02:00
return new Type\Union([
new Type\Atomic\TArray([
$key_type,
2017-05-27 02:05:57 +02:00
$inner_type,
]),
]);
2016-11-02 14:24:36 +01:00
}
2016-11-02 14:24:36 +01:00
if ($array_arg_type) {
$inner_type = clone $array_arg_type->type_params[1];
2017-05-25 04:07:49 +02:00
return new Type\Union([
new Type\Atomic\TArray([
$key_type,
2017-05-27 02:05:57 +02:00
$inner_type,
]),
]);
2016-10-22 23:35:59 +02:00
}
} elseif ($function_call_arg->value instanceof PhpParser\Node\Scalar\String_
|| $function_call_arg->value instanceof PhpParser\Node\Expr\Array_
) {
$mapping_function_ids = Statements\Expression\CallChecker::getFunctionIdsFromCallableArg(
$statements_checker,
$function_call_arg->value
);
2016-11-02 14:24:36 +01:00
$call_map = self::getCallMap();
$mapping_return_type = null;
$project_checker = $statements_checker->getFileChecker()->project_checker;
foreach ($mapping_function_ids as $mapping_function_id) {
if (isset($call_map[$mapping_function_id][0])) {
if ($call_map[$mapping_function_id][0]) {
$mapped_function_return = Type::parseString($call_map[$mapping_function_id][0]);
2017-05-25 04:07:49 +02:00
if ($mapping_return_type) {
$mapping_return_type = Type::combineUnionTypes(
$mapping_return_type,
$mapped_function_return
);
} else {
$mapping_return_type = $mapped_function_return;
}
}
} else {
if (strpos($mapping_function_id, '::') !== false) {
$return_type = MethodChecker::getMethodReturnType(
$project_checker,
$mapping_function_id
) ?: Type::getMixed();
if ($mapping_return_type) {
$mapping_return_type = Type::combineUnionTypes(
$mapping_return_type,
$return_type
);
} else {
$mapping_return_type = $return_type;
}
} else {
$function_storage = FunctionChecker::getStorage(
$statements_checker,
$mapping_function_id
);
$return_type = $function_storage->return_type ?: Type::getMixed();
if ($mapping_return_type) {
$mapping_return_type = Type::combineUnionTypes(
$mapping_return_type,
$return_type
);
} else {
$mapping_return_type = $return_type;
}
}
2016-11-02 14:24:36 +01:00
}
}
if ($mapping_return_type) {
return new Type\Union([
new Type\Atomic\TArray([
Type::getInt(),
$mapping_return_type,
]),
]);
}
2016-10-22 23:35:59 +02:00
}
}
2016-11-02 14:24:36 +01:00
return Type::getArray();
2016-08-22 21:00:12 +02:00
}
/**
* Gets the method/function call map
*
* @return array<string, array<string, string>>
* @psalm-suppress MixedInferredReturnType as the use of require buggers things up
2017-01-09 05:58:06 +01:00
* @psalm-suppress MixedAssignment
* @psalm-suppress MoreSpecificReturnType
2016-08-22 21:00:12 +02:00
*/
protected static function getCallMap()
{
if (self::$call_map !== null) {
return self::$call_map;
}
/** @var array<string, array<string, string>> */
2017-05-25 04:07:49 +02:00
$call_map = require_once(__DIR__ . '/../CallMap.php');
2016-08-22 21:00:12 +02:00
self::$call_map = [];
foreach ($call_map as $key => $value) {
2016-10-22 23:35:59 +02:00
$cased_key = strtolower($key);
self::$call_map[$cased_key] = $value;
2016-08-22 21:00:12 +02:00
}
return self::$call_map;
}
2016-10-21 00:12:13 +02:00
2016-11-02 07:29:00 +01:00
/**
* @param string $key
2017-05-27 02:16:18 +02:00
*
2016-11-02 07:29:00 +01:00
* @return bool
*/
2016-10-22 23:35:59 +02:00
public static function inCallMap($key)
{
return isset(self::getCallMap()[strtolower($key)]);
}
2017-01-12 15:42:24 +01:00
/**
* @param string $function_name
* @param StatementsSource $source
2017-05-27 02:16:18 +02:00
*
2017-01-12 15:42:24 +01:00
* @return string
*/
public static function getFQFunctionNameFromString($function_name, StatementsSource $source)
{
if (empty($function_name)) {
throw new \InvalidArgumentException('$function_name cannot be empty');
}
if ($function_name[0] === '\\') {
return substr($function_name, 1);
}
$function_name_lcase = strtolower($function_name);
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
$aliases = $source->getAliases();
$imported_function_namespaces = $aliases->functions;
$imported_namespaces = $aliases->uses;
2017-01-12 15:42:24 +01:00
if (strpos($function_name, '\\') !== false) {
$function_name_parts = explode('\\', $function_name);
$first_namespace = array_shift($function_name_parts);
$first_namespace_lcase = strtolower($first_namespace);
2017-01-12 15:42:24 +01:00
if (isset($imported_namespaces[$first_namespace_lcase])) {
return $imported_namespaces[$first_namespace_lcase] . '\\' . implode('\\', $function_name_parts);
2017-01-12 15:42:24 +01:00
}
if (isset($imported_function_namespaces[$first_namespace_lcase])) {
return $imported_function_namespaces[$first_namespace_lcase] . '\\' .
implode('\\', $function_name_parts);
}
} elseif (isset($imported_namespaces[$function_name_lcase])) {
return $imported_namespaces[$function_name_lcase];
} elseif (isset($imported_function_namespaces[$function_name_lcase])) {
return $imported_function_namespaces[$function_name_lcase];
2017-01-12 15:42:24 +01:00
}
$namespace = $source->getNamespace();
return ($namespace ? $namespace . '\\' : '') . $function_name;
}
2016-11-02 07:29:00 +01:00
/**
* @return void
*/
2016-10-21 00:12:13 +02:00
public static function clearCache()
{
self::$builtin_functions = [];
self::$stubbed_functions = [];
2016-10-21 00:12:13 +02:00
}
2016-01-08 00:28:27 +01:00
}