1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Merge branch '4.x' into update-master

This commit is contained in:
Bruce Weirdan 2022-05-28 14:49:12 -04:00
commit e0acf22e40
No known key found for this signature in database
GPG Key ID: CFC3AAB181751B0D
26 changed files with 607 additions and 171 deletions

View File

@ -36,8 +36,7 @@
"sebastian/diff": "^4.0",
"symfony/console": "^3.4.17 || ^4.1.6 || ^5.0 || ^6.0",
"symfony/filesystem": "^5.4 || ^6.0",
"symfony/polyfill-php80": "^1.25",
"webmozart/path-util": "^2.3"
"symfony/polyfill-php80": "^1.25"
},
"provide": {
"psalm/psalm": "self.version"

View File

@ -418,7 +418,9 @@ return [
'array_uintersect_assoc\'1' => ['associative-array', 'array'=>'array', 'rest'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable', '...rest='=>'array|callable(mixed,mixed):int'],
'array_uintersect_uassoc' => ['associative-array', 'array'=>'array', 'rest'=>'array', 'data_compare_func'=>'callable(mixed,mixed):int', 'key_compare_func'=>'callable(mixed,mixed):int'],
'array_uintersect_uassoc\'1' => ['associative-array', 'array'=>'array', 'rest'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', 'arg5'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'],
'array_unique' => ['associative-array', 'array'=>'array', 'flags='=>'int'],
'array_unique' => ['array', 'array'=>'array', 'flags='=>'0'],
'array_unique\'1' => ['array<int|float|string|null>', 'array'=>'array<int|float|string|null>', 'flags='=>'1'],
'array_unique\'2' => ['array<int|float|string|bool|\Stringable|null>', 'array'=>'array<int|float|string|bool|\Stringable|null>', 'flags='=>'2|5'],
'array_unshift' => ['int', '&rw_array'=>'array', 'values'=>'mixed', '...vars='=>'mixed'],
'array_values' => ['list<mixed>', 'array'=>'array'],
'array_walk' => ['bool', '&rw_array'=>'array', 'callback'=>'callable', 'arg='=>'mixed'],

View File

@ -9561,7 +9561,9 @@ return [
'array_uintersect_assoc\'1' => ['associative-array', 'array'=>'array', 'rest'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable', '...rest='=>'array|callable(mixed,mixed):int'],
'array_uintersect_uassoc' => ['associative-array', 'array'=>'array', 'rest'=>'array', 'data_compare_func'=>'callable(mixed,mixed):int', 'key_compare_func'=>'callable(mixed,mixed):int'],
'array_uintersect_uassoc\'1' => ['associative-array', 'array'=>'array', 'rest'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', 'arg5'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'],
'array_unique' => ['associative-array', 'array'=>'array', 'flags='=>'int'],
'array_unique' => ['array', 'array'=>'array', 'flags='=>'0'],
'array_unique\'1' => ['array<int|float|string|null>', 'array'=>'array<int|float|string|null>', 'flags='=>'1'],
'array_unique\'2' => ['array<int|float|string|bool|\Stringable|null>', 'array'=>'array<int|float|string|bool|\Stringable|null>', 'flags='=>'2|5'],
'array_unshift' => ['int', '&rw_array'=>'array', 'values'=>'mixed', '...vars='=>'mixed'],
'array_values' => ['list<mixed>', 'array'=>'array'],
'array_walk' => ['bool', '&rw_array'=>'array', 'callback'=>'callable', 'arg='=>'mixed'],

View File

@ -2130,6 +2130,11 @@ class Config
$this->internal_stubs[] = $ext_phpredis_path;
}
if (extension_loaded('apcu')) {
$ext_apcu_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'ext-apcu.phpstub';
$this->internal_stubs[] = $ext_apcu_path;
}
foreach ($this->internal_stubs as $stub_path) {
if (!file_exists($stub_path)) {
throw new UnexpectedValueException('Cannot locate ' . $stub_path);

View File

@ -192,22 +192,42 @@ class CastAnalyzer
}
if ($stmt instanceof PhpParser\Node\Expr\Cast\Object_) {
$was_inside_general_use = $context->inside_general_use;
$context->inside_general_use = true;
if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) {
$context->inside_general_use = $was_inside_general_use;
if (!self::checkExprGeneralUse($statements_analyzer, $stmt, $context)) {
return false;
}
$context->inside_general_use = $was_inside_general_use;
$type = new Union([new TNamedObject('stdClass')]);
$permissible_atomic_types = [];
$all_permissible = false;
$maybe_type = $statements_analyzer->node_data->getType($stmt->expr);
if ($stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr)) {
if ($stmt_expr_type->isObjectType()) {
self::handleRedundantCast($stmt_expr_type, $statements_analyzer, $stmt);
}
$all_permissible = true;
foreach ($stmt_expr_type->getAtomicTypes() as $type) {
if ($type instanceof Scalar) {
$objWithProps = new TObjectWithProperties(['scalar' => new Union([$type])]);
$permissible_atomic_types[] = $objWithProps;
} elseif ($type instanceof TKeyedArray) {
$permissible_atomic_types[] = new TObjectWithProperties($type->properties);
} else {
$all_permissible = false;
break;
}
}
}
if ($permissible_atomic_types && $all_permissible) {
$type = TypeCombiner::combine($permissible_atomic_types);
} else {
$type = Type::getObject();
}
if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph
) {
$type->parent_nodes = $maybe_type->parent_nodes ?? [];
$type->parent_nodes = $stmt_expr_type->parent_nodes ?? [];
}
$statements_analyzer->node_data->setType($stmt, $type);
@ -216,14 +236,9 @@ class CastAnalyzer
}
if ($stmt instanceof PhpParser\Node\Expr\Cast\Array_) {
$was_inside_general_use = $context->inside_general_use;
$context->inside_general_use = true;
if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) {
$context->inside_general_use = $was_inside_general_use;
if (!self::checkExprGeneralUse($statements_analyzer, $stmt, $context)) {
return false;
}
$context->inside_general_use = $was_inside_general_use;
$permissible_atomic_types = [];
$all_permissible = false;
@ -466,6 +481,18 @@ class CastAnalyzer
return $str_type;
}
private static function checkExprGeneralUse(
StatementsAnalyzer $statements_analyzer,
PhpParser\Node\Expr\Cast $stmt,
Context $context
): bool {
$was_inside_general_use = $context->inside_general_use;
$context->inside_general_use = true;
$retVal = ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context);
$context->inside_general_use = $was_inside_general_use;
return $retVal;
}
private static function handleRedundantCast(
Union $maybe_type,
StatementsAnalyzer $statements_analyzer,

View File

@ -9,7 +9,6 @@ use Psalm\Internal\PluginManager\Command\EnableCommand;
use Psalm\Internal\PluginManager\Command\ShowCommand;
use Psalm\Internal\PluginManager\PluginListFactory;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputOption;
use function dirname;
use function getcwd;
@ -44,10 +43,6 @@ final class Plugin
new DisableCommand($plugin_list_factory),
]);
$app->getDefinition()->addOption(
new InputOption('config', 'c', InputOption::VALUE_REQUIRED, 'Path to Psalm config file')
);
$app->setDefaultCommand('show');
$app->run();
}

View File

@ -380,7 +380,6 @@ class TextDocument
$indentation = $matches[1] ?? '';
}
/**
* Suppress Psalm because ther are bugs in how
* LanguageServer's signature of WorkspaceEdit is declared:

View File

@ -7,6 +7,7 @@ use Psalm\Internal\PluginManager\PluginListFactory;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use UnexpectedValueException;
@ -41,6 +42,7 @@ class DisableCommand extends Command
InputArgument::REQUIRED,
'Plugin name (fully qualified class name or composer package name)'
)
->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Path to Psalm config file')
->addUsage('vendor/plugin-package-name [-c path/to/psalm.xml]');
$this->addUsage('\'Plugin\Class\Name\' [-c path/to/psalm.xml]');
}

View File

@ -7,6 +7,7 @@ use Psalm\Internal\PluginManager\PluginListFactory;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use UnexpectedValueException;
@ -41,6 +42,7 @@ class EnableCommand extends Command
InputArgument::REQUIRED,
'Plugin name (fully qualified class name or composer package name)'
)
->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Path to Psalm config file')
->addUsage('vendor/plugin-package-name [-c path/to/psalm.xml]');
$this->addUsage('\'Plugin\Class\Name\' [-c path/to/psalm.xml]');
}

View File

@ -5,6 +5,7 @@ namespace Psalm\Internal\PluginManager\Command;
use Psalm\Internal\PluginManager\PluginListFactory;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use UnexpectedValueException;
@ -37,6 +38,7 @@ class ShowCommand extends Command
$this
->setName('show')
->setDescription('Lists enabled and available plugins')
->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Path to Psalm config file')
->addUsage('[-c path/to/psalm.xml]');
}

View File

@ -13,17 +13,18 @@ use function file_get_contents;
use function file_put_contents;
use function filemtime;
use function get_class;
use function hash;
use function igbinary_serialize;
use function igbinary_unserialize;
use function is_dir;
use function mkdir;
use function serialize;
use function sha1;
use function strtolower;
use function unlink;
use function unserialize;
use const DIRECTORY_SEPARATOR;
use const PHP_VERSION_ID;
/**
* @internal
@ -111,7 +112,8 @@ class ClassLikeStorageCacheProvider
private function getCacheHash(?string $file_path, ?string $file_contents): string
{
return sha1(($file_path ? $file_contents : '') . $this->modified_timestamps);
$data = ($file_path ? $file_contents : '') . $this->modified_timestamps;
return PHP_VERSION_ID >= 80100 ? hash('xxh128', $data) : hash('md4', $data);
}
/**
@ -161,9 +163,13 @@ class ClassLikeStorageCacheProvider
mkdir($parser_cache_directory, 0777, true);
}
$data = $file_path ? strtolower($file_path) . ' ' : '';
$data .= $fq_classlike_name_lc;
$file_path_sha = PHP_VERSION_ID >= 80100 ? hash('xxh128', $data) : hash('md4', $data);
return $parser_cache_directory
. DIRECTORY_SEPARATOR
. sha1(($file_path ? strtolower($file_path) . ' ' : '') . $fq_classlike_name_lc)
. $file_path_sha
. ($this->config->use_igbinary ? '-igbinary' : '');
}
}

View File

@ -4,7 +4,6 @@ namespace Psalm\Internal\Provider;
use function microtime;
use function strpos;
use function strtolower;
/**
* @internal
@ -28,8 +27,8 @@ class FakeFileProvider extends FileProvider
public function getContents(string $file_path, bool $go_to_source = false): string
{
if (!$go_to_source && isset($this->temp_files[strtolower($file_path)])) {
return $this->temp_files[strtolower($file_path)];
if (!$go_to_source && isset($this->temp_files[$file_path])) {
return $this->temp_files[$file_path];
}
return $this->fake_files[$file_path] ?? parent::getContents($file_path);
@ -42,8 +41,8 @@ class FakeFileProvider extends FileProvider
public function setOpenContents(string $file_path, string $file_contents): void
{
if (isset($this->fake_files[strtolower($file_path)])) {
$this->fake_files[strtolower($file_path)] = $file_contents;
if (isset($this->fake_files[$file_path])) {
$this->fake_files[$file_path] = $file_contents;
}
}
@ -69,7 +68,7 @@ class FakeFileProvider extends FileProvider
$file_paths = parent::getFilesInDir($dir_path, $file_extensions, $filter);
foreach ($this->fake_files as $file_path => $_) {
if (strpos(strtolower($file_path), strtolower($dir_path)) === 0) {
if (strpos($file_path, $dir_path) === 0) {
$file_paths[] = $file_path;
}
}

View File

@ -15,7 +15,6 @@ use function file_put_contents;
use function filemtime;
use function in_array;
use function is_dir;
use function strtolower;
use const DIRECTORY_SEPARATOR;
@ -25,24 +24,23 @@ use const DIRECTORY_SEPARATOR;
class FileProvider
{
/**
* @var array<lowercase-string, string>
* @var array<string, string>
*/
protected $temp_files = [];
/**
* @var array<lowercase-string, string>
* @var array<string, string>
*/
protected $open_files = [];
protected static $open_files = [];
public function getContents(string $file_path, bool $go_to_source = false): string
{
$file_path_lc = strtolower($file_path);
if (!$go_to_source && isset($this->temp_files[$file_path_lc])) {
return $this->temp_files[$file_path_lc];
if (!$go_to_source && isset($this->temp_files[$file_path])) {
return $this->temp_files[$file_path];
}
if (isset($this->open_files[$file_path_lc])) {
return $this->open_files[$file_path_lc];
if (isset(self::$open_files[$file_path])) {
return self::$open_files[$file_path];
}
if (!file_exists($file_path)) {
@ -53,18 +51,21 @@ class FileProvider
throw new UnexpectedValueException('File ' . $file_path . ' is a directory');
}
return (string)file_get_contents($file_path);
$file_contents = (string) file_get_contents($file_path);
self::$open_files[$file_path] = $file_contents;
return $file_contents;
}
public function setContents(string $file_path, string $file_contents): void
{
$file_path_lc = strtolower($file_path);
if (isset($this->open_files[$file_path_lc])) {
$this->open_files[$file_path_lc] = $file_contents;
if (isset(self::$open_files[$file_path])) {
self::$open_files[$file_path] = $file_contents;
}
if (isset($this->temp_files[$file_path_lc])) {
$this->temp_files[$file_path_lc] = $file_contents;
if (isset($this->temp_files[$file_path])) {
$this->temp_files[$file_path] = $file_contents;
}
file_put_contents($file_path, $file_contents);
@ -72,9 +73,8 @@ class FileProvider
public function setOpenContents(string $file_path, string $file_contents): void
{
$file_path_lc = strtolower($file_path);
if (isset($this->open_files[$file_path_lc])) {
$this->open_files[$file_path_lc] = $file_contents;
if (isset(self::$open_files[$file_path])) {
self::$open_files[$file_path] = $file_contents;
}
}
@ -84,34 +84,32 @@ class FileProvider
throw new UnexpectedValueException('File should exist to get modified time');
}
return (int)filemtime($file_path);
return (int) filemtime($file_path);
}
public function addTemporaryFileChanges(string $file_path, string $new_content): void
{
$this->temp_files[strtolower($file_path)] = $new_content;
$this->temp_files[$file_path] = $new_content;
}
public function removeTemporaryFileChanges(string $file_path): void
{
unset($this->temp_files[strtolower($file_path)]);
unset($this->temp_files[$file_path]);
}
public function openFile(string $file_path): void
{
$this->open_files[strtolower($file_path)] = $this->getContents($file_path, true);
self::$open_files[$file_path] = $this->getContents($file_path, true);
}
public function isOpen(string $file_path): bool
{
$file_path_lc = strtolower($file_path);
return isset($this->temp_files[$file_path_lc]) || isset($this->open_files[$file_path_lc]);
return isset($this->temp_files[$file_path]) || isset(self::$open_files[$file_path]);
}
public function closeFile(string $file_path): void
{
$file_path_lc = strtolower($file_path);
unset($this->temp_files[$file_path_lc], $this->open_files[$file_path_lc]);
unset($this->temp_files[$file_path], self::$open_files[$file_path]);
}
public function fileExists(string $file_path): bool

View File

@ -9,6 +9,8 @@ use UnexpectedValueException;
use function file_exists;
use function file_get_contents;
use function file_put_contents;
use function igbinary_serialize;
use function igbinary_unserialize;
use function is_array;
use function is_readable;
use function mkdir;
@ -83,7 +85,11 @@ class FileReferenceCacheProvider
return null;
}
if ($this->config->use_igbinary) {
$reference_cache = igbinary_unserialize((string) file_get_contents($reference_cache_location));
} else {
$reference_cache = unserialize((string) file_get_contents($reference_cache_location));
}
if (!is_array($reference_cache)) {
throw new UnexpectedValueException('The reference cache must be an array');
@ -109,7 +115,11 @@ class FileReferenceCacheProvider
return null;
}
if ($this->config->use_igbinary) {
$reference_cache = igbinary_unserialize((string) file_get_contents($reference_cache_location));
} else {
$reference_cache = unserialize((string) file_get_contents($reference_cache_location));
}
if (!is_array($reference_cache)) {
throw new UnexpectedValueException('The reference cache must be an array');
@ -135,7 +145,11 @@ class FileReferenceCacheProvider
return null;
}
if ($this->config->use_igbinary) {
$reference_cache = igbinary_unserialize((string) file_get_contents($reference_cache_location));
} else {
$reference_cache = unserialize((string) file_get_contents($reference_cache_location));
}
if (!is_array($reference_cache)) {
throw new UnexpectedValueException('The reference cache must be an array');
@ -155,13 +169,17 @@ class FileReferenceCacheProvider
return null;
}
$cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_CLASS_REFERENCE_CACHE_NAME;
$reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_CLASS_REFERENCE_CACHE_NAME;
if (!is_readable($cache_location)) {
if (!is_readable($reference_cache_location)) {
return null;
}
$reference_cache = unserialize((string) file_get_contents($cache_location));
if ($this->config->use_igbinary) {
$reference_cache = igbinary_unserialize((string) file_get_contents($reference_cache_location));
} else {
$reference_cache = unserialize((string) file_get_contents($reference_cache_location));
}
if (!is_array($reference_cache)) {
throw new UnexpectedValueException('The reference cache must be an array');
@ -187,7 +205,12 @@ class FileReferenceCacheProvider
return null;
}
$class_member_reference_cache = unserialize((string) file_get_contents($class_member_cache_location));
$class_member_reference_cache = (string) file_get_contents($class_member_cache_location);
if ($this->config->use_igbinary) {
$class_member_reference_cache = igbinary_unserialize($class_member_reference_cache);
} else {
$class_member_reference_cache = unserialize($class_member_reference_cache);
}
if (!is_array($class_member_reference_cache)) {
throw new UnexpectedValueException('The reference cache must be an array');
@ -214,7 +237,12 @@ class FileReferenceCacheProvider
return null;
}
$method_dependencies_cache = unserialize((string) file_get_contents($method_dependencies_cache_location));
$method_dependencies_cache = (string) file_get_contents($method_dependencies_cache_location);
if ($this->config->use_igbinary) {
$method_dependencies_cache = igbinary_unserialize($method_dependencies_cache);
} else {
$method_dependencies_cache = unserialize($method_dependencies_cache);
}
if (!is_array($method_dependencies_cache)) {
throw new UnexpectedValueException('The reference cache must be an array');
@ -240,7 +268,12 @@ class FileReferenceCacheProvider
return null;
}
$class_member_reference_cache = unserialize((string) file_get_contents($class_member_cache_location));
$class_member_reference_cache = (string) file_get_contents($class_member_cache_location);
if ($this->config->use_igbinary) {
$class_member_reference_cache = igbinary_unserialize($class_member_reference_cache);
} else {
$class_member_reference_cache = unserialize($class_member_reference_cache);
}
if (!is_array($class_member_reference_cache)) {
throw new UnexpectedValueException('The reference cache must be an array');
@ -266,7 +299,12 @@ class FileReferenceCacheProvider
return null;
}
$class_member_reference_cache = unserialize((string) file_get_contents($class_member_cache_location));
$class_member_reference_cache = (string) file_get_contents($class_member_cache_location);
if ($this->config->use_igbinary) {
$class_member_reference_cache = igbinary_unserialize($class_member_reference_cache);
} else {
$class_member_reference_cache = unserialize($class_member_reference_cache);
}
if (!is_array($class_member_reference_cache)) {
throw new UnexpectedValueException('The reference cache must be an array');
@ -292,7 +330,12 @@ class FileReferenceCacheProvider
return null;
}
$class_member_reference_cache = unserialize((string) file_get_contents($class_member_cache_location));
$class_member_reference_cache = (string) file_get_contents($class_member_cache_location);
if ($this->config->use_igbinary) {
$class_member_reference_cache = igbinary_unserialize($class_member_reference_cache);
} else {
$class_member_reference_cache = unserialize($class_member_reference_cache);
}
if (!is_array($class_member_reference_cache)) {
throw new UnexpectedValueException('The reference cache must be an array');
@ -318,7 +361,12 @@ class FileReferenceCacheProvider
return null;
}
$file_class_member_reference_cache = unserialize((string) file_get_contents($file_class_member_cache_location));
$file_class_member_reference_cache = (string) file_get_contents($file_class_member_cache_location);
if ($this->config->use_igbinary) {
$file_class_member_reference_cache = igbinary_unserialize($file_class_member_reference_cache);
} else {
$file_class_member_reference_cache = unserialize($file_class_member_reference_cache);
}
if (!is_array($file_class_member_reference_cache)) {
throw new UnexpectedValueException('The reference cache must be an array');
@ -346,7 +394,12 @@ class FileReferenceCacheProvider
return null;
}
$file_class_member_reference_cache = unserialize((string) file_get_contents($file_class_member_cache_location));
$file_class_member_reference_cache = (string) file_get_contents($file_class_member_cache_location);
if ($this->config->use_igbinary) {
$file_class_member_reference_cache = igbinary_unserialize($file_class_member_reference_cache);
} else {
$file_class_member_reference_cache = unserialize($file_class_member_reference_cache);
}
if (!is_array($file_class_member_reference_cache)) {
throw new UnexpectedValueException('The reference cache must be an array');
@ -374,7 +427,12 @@ class FileReferenceCacheProvider
return null;
}
$file_class_member_reference_cache = unserialize((string) file_get_contents($file_class_member_cache_location));
$file_class_member_reference_cache = (string) file_get_contents($file_class_member_cache_location);
if ($this->config->use_igbinary) {
$file_class_member_reference_cache = igbinary_unserialize($file_class_member_reference_cache);
} else {
$file_class_member_reference_cache = unserialize($file_class_member_reference_cache);
}
if (!is_array($file_class_member_reference_cache)) {
throw new UnexpectedValueException('The reference cache must be an array');
@ -401,7 +459,12 @@ class FileReferenceCacheProvider
return null;
}
$file_class_member_reference_cache = unserialize((string) file_get_contents($file_class_member_cache_location));
$file_class_member_reference_cache = (string) file_get_contents($file_class_member_cache_location);
if ($this->config->use_igbinary) {
$file_class_member_reference_cache = igbinary_unserialize($file_class_member_reference_cache);
} else {
$file_class_member_reference_cache = unserialize($file_class_member_reference_cache);
}
if (!is_array($file_class_member_reference_cache)) {
throw new UnexpectedValueException('The reference cache must be an array');
@ -421,19 +484,23 @@ class FileReferenceCacheProvider
return null;
}
$cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::UNKNOWN_MEMBER_CACHE_NAME;
$reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::UNKNOWN_MEMBER_CACHE_NAME;
if (!is_readable($cache_location)) {
if (!is_readable($reference_cache_location)) {
return null;
}
$cache = unserialize((string) file_get_contents($cache_location));
if ($this->config->use_igbinary) {
$reference_cache = igbinary_unserialize((string) file_get_contents($reference_cache_location));
} else {
$reference_cache = unserialize((string) file_get_contents($reference_cache_location));
}
if (!is_array($cache)) {
if (!is_array($reference_cache)) {
throw new UnexpectedValueException('The reference cache must be an array');
}
return $cache;
return $reference_cache;
}
/**
@ -447,19 +514,23 @@ class FileReferenceCacheProvider
return null;
}
$cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_PARAM_USE_CACHE_NAME;
$reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_PARAM_USE_CACHE_NAME;
if (!is_readable($cache_location)) {
if (!is_readable($reference_cache_location)) {
return null;
}
$cache = unserialize((string) file_get_contents($cache_location));
if ($this->config->use_igbinary) {
$reference_cache = igbinary_unserialize((string) file_get_contents($reference_cache_location));
} else {
$reference_cache = unserialize((string) file_get_contents($reference_cache_location));
}
if (!is_array($cache)) {
if (!is_array($reference_cache)) {
throw new UnexpectedValueException('The method param use cache must be an array');
}
return $cache;
return $reference_cache;
}
/**
@ -479,7 +550,11 @@ class FileReferenceCacheProvider
return null;
}
if ($this->config->use_igbinary) {
$issues_cache = igbinary_unserialize((string) file_get_contents($issues_cache_location));
} else {
$issues_cache = unserialize((string) file_get_contents($issues_cache_location));
}
if (!is_array($issues_cache)) {
throw new UnexpectedValueException('The reference cache must be an array');
@ -498,8 +573,12 @@ class FileReferenceCacheProvider
$reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::REFERENCE_CACHE_NAME;
if ($this->config->use_igbinary) {
file_put_contents($reference_cache_location, igbinary_serialize($file_references));
} else {
file_put_contents($reference_cache_location, serialize($file_references));
}
}
public function setCachedClassLikeFiles(array $file_references): void
{
@ -511,8 +590,12 @@ class FileReferenceCacheProvider
$reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::CLASSLIKE_FILE_CACHE_NAME;
if ($this->config->use_igbinary) {
file_put_contents($reference_cache_location, igbinary_serialize($file_references));
} else {
file_put_contents($reference_cache_location, serialize($file_references));
}
}
public function setCachedNonMethodClassReferences(array $file_class_references): void
{
@ -524,8 +607,12 @@ class FileReferenceCacheProvider
$reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::NONMETHOD_CLASS_REFERENCE_CACHE_NAME;
if ($this->config->use_igbinary) {
file_put_contents($reference_cache_location, igbinary_serialize($file_class_references));
} else {
file_put_contents($reference_cache_location, serialize($file_class_references));
}
}
public function setCachedMethodClassReferences(array $method_class_references): void
{
@ -537,8 +624,12 @@ class FileReferenceCacheProvider
$reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_CLASS_REFERENCE_CACHE_NAME;
if ($this->config->use_igbinary) {
file_put_contents($reference_cache_location, igbinary_serialize($method_class_references));
} else {
file_put_contents($reference_cache_location, serialize($method_class_references));
}
}
public function setCachedMethodMemberReferences(array $member_references): void
{
@ -550,8 +641,12 @@ class FileReferenceCacheProvider
$member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::CLASS_METHOD_CACHE_NAME;
if ($this->config->use_igbinary) {
file_put_contents($member_cache_location, igbinary_serialize($member_references));
} else {
file_put_contents($member_cache_location, serialize($member_references));
}
}
public function setCachedMethodDependencies(array $member_references): void
{
@ -563,8 +658,12 @@ class FileReferenceCacheProvider
$member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_DEPENDENCIES_CACHE_NAME;
if ($this->config->use_igbinary) {
file_put_contents($member_cache_location, igbinary_serialize($member_references));
} else {
file_put_contents($member_cache_location, serialize($member_references));
}
}
public function setCachedMethodPropertyReferences(array $property_references): void
{
@ -576,8 +675,12 @@ class FileReferenceCacheProvider
$member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::CLASS_PROPERTY_CACHE_NAME;
if ($this->config->use_igbinary) {
file_put_contents($member_cache_location, igbinary_serialize($property_references));
} else {
file_put_contents($member_cache_location, serialize($property_references));
}
}
public function setCachedMethodMethodReturnReferences(array $method_return_references): void
{
@ -589,8 +692,12 @@ class FileReferenceCacheProvider
$member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::CLASS_METHOD_RETURN_CACHE_NAME;
if ($this->config->use_igbinary) {
file_put_contents($member_cache_location, igbinary_serialize($method_return_references));
} else {
file_put_contents($member_cache_location, serialize($method_return_references));
}
}
public function setCachedMethodMissingMemberReferences(array $member_references): void
{
@ -602,8 +709,12 @@ class FileReferenceCacheProvider
$member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_MISSING_MEMBER_CACHE_NAME;
if ($this->config->use_igbinary) {
file_put_contents($member_cache_location, igbinary_serialize($member_references));
} else {
file_put_contents($member_cache_location, serialize($member_references));
}
}
public function setCachedFileMemberReferences(array $member_references): void
{
@ -615,8 +726,12 @@ class FileReferenceCacheProvider
$member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::FILE_CLASS_MEMBER_CACHE_NAME;
if ($this->config->use_igbinary) {
file_put_contents($member_cache_location, igbinary_serialize($member_references));
} else {
file_put_contents($member_cache_location, serialize($member_references));
}
}
public function setCachedFilePropertyReferences(array $property_references): void
{
@ -628,8 +743,12 @@ class FileReferenceCacheProvider
$member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::FILE_CLASS_PROPERTY_CACHE_NAME;
if ($this->config->use_igbinary) {
file_put_contents($member_cache_location, igbinary_serialize($property_references));
} else {
file_put_contents($member_cache_location, serialize($property_references));
}
}
public function setCachedFileMethodReturnReferences(array $method_return_references): void
{
@ -641,8 +760,12 @@ class FileReferenceCacheProvider
$member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::FILE_METHOD_RETURN_CACHE_NAME;
if ($this->config->use_igbinary) {
file_put_contents($member_cache_location, igbinary_serialize($method_return_references));
} else {
file_put_contents($member_cache_location, serialize($method_return_references));
}
}
public function setCachedFileMissingMemberReferences(array $member_references): void
{
@ -654,8 +777,12 @@ class FileReferenceCacheProvider
$member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::FILE_MISSING_MEMBER_CACHE_NAME;
if ($this->config->use_igbinary) {
file_put_contents($member_cache_location, igbinary_serialize($member_references));
} else {
file_put_contents($member_cache_location, serialize($member_references));
}
}
public function setCachedMixedMemberNameReferences(array $references): void
{
@ -665,9 +792,13 @@ class FileReferenceCacheProvider
return;
}
$cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::UNKNOWN_MEMBER_CACHE_NAME;
$reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::UNKNOWN_MEMBER_CACHE_NAME;
file_put_contents($cache_location, serialize($references));
if ($this->config->use_igbinary) {
file_put_contents($reference_cache_location, igbinary_serialize($references));
} else {
file_put_contents($reference_cache_location, serialize($references));
}
}
public function setCachedMethodParamUses(array $uses): void
@ -678,9 +809,13 @@ class FileReferenceCacheProvider
return;
}
$cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_PARAM_USE_CACHE_NAME;
$reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_PARAM_USE_CACHE_NAME;
file_put_contents($cache_location, serialize($uses));
if ($this->config->use_igbinary) {
file_put_contents($reference_cache_location, igbinary_serialize($uses));
} else {
file_put_contents($reference_cache_location, serialize($uses));
}
}
public function setCachedIssues(array $issues): void
@ -693,8 +828,12 @@ class FileReferenceCacheProvider
$issues_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::ISSUES_CACHE_NAME;
if ($this->config->use_igbinary) {
file_put_contents($issues_cache_location, igbinary_serialize($issues));
} else {
file_put_contents($issues_cache_location, serialize($issues));
}
}
/**
* @return array<string, array<string, int>>|false
@ -708,9 +847,14 @@ class FileReferenceCacheProvider
if ($cache_directory
&& file_exists($analyzed_methods_cache_location)
) {
if ($this->config->use_igbinary) {
/** @var array<string, array<string, int>> */
return igbinary_unserialize(file_get_contents($analyzed_methods_cache_location));
} else {
/** @var array<string, array<string, int>> */
return unserialize(file_get_contents($analyzed_methods_cache_location));
}
}
return false;
}
@ -727,10 +871,11 @@ class FileReferenceCacheProvider
. DIRECTORY_SEPARATOR
. self::ANALYZED_METHODS_CACHE_NAME;
file_put_contents(
$analyzed_methods_cache_location,
serialize($analyzed_methods)
);
if ($this->config->use_igbinary) {
file_put_contents($analyzed_methods_cache_location, igbinary_serialize($analyzed_methods));
} else {
file_put_contents($analyzed_methods_cache_location, serialize($analyzed_methods));
}
}
}
@ -746,10 +891,17 @@ class FileReferenceCacheProvider
if ($cache_directory
&& file_exists($file_maps_cache_location)
) {
if ($this->config->use_igbinary) {
/**
* @var array<string, FileMapType>
*/
$file_maps_cache = igbinary_unserialize(file_get_contents($file_maps_cache_location));
} else {
/**
* @var array<string, FileMapType>
*/
$file_maps_cache = unserialize(file_get_contents($file_maps_cache_location));
}
return $file_maps_cache;
}
@ -767,10 +919,11 @@ class FileReferenceCacheProvider
if ($cache_directory) {
$file_maps_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::FILE_MAPS_CACHE_NAME;
file_put_contents(
$file_maps_cache_location,
serialize($file_maps)
);
if ($this->config->use_igbinary) {
file_put_contents($file_maps_cache_location, igbinary_serialize($file_maps));
} else {
file_put_contents($file_maps_cache_location, serialize($file_maps));
}
}
}
@ -786,8 +939,13 @@ class FileReferenceCacheProvider
if ($cache_directory
&& file_exists($type_coverage_cache_location)
) {
if ($this->config->use_igbinary) {
/** @var array<string, array{int, int}> */
$type_coverage_cache = igbinary_unserialize(file_get_contents($type_coverage_cache_location));
} else {
/** @var array<string, array{int, int}> */
$type_coverage_cache = unserialize(file_get_contents($type_coverage_cache_location));
}
return $type_coverage_cache;
}
@ -805,10 +963,11 @@ class FileReferenceCacheProvider
if ($cache_directory) {
$type_coverage_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::TYPE_COVERAGE_CACHE_NAME;
file_put_contents(
$type_coverage_cache_location,
serialize($mixed_counts)
);
if ($this->config->use_igbinary) {
file_put_contents($type_coverage_cache_location, igbinary_serialize($mixed_counts));
} else {
file_put_contents($type_coverage_cache_location, serialize($mixed_counts));
}
}
}

View File

@ -13,17 +13,18 @@ use function file_get_contents;
use function file_put_contents;
use function filemtime;
use function get_class;
use function hash;
use function igbinary_serialize;
use function igbinary_unserialize;
use function is_dir;
use function mkdir;
use function serialize;
use function sha1;
use function strtolower;
use function unlink;
use function unserialize;
use const DIRECTORY_SEPARATOR;
use const PHP_VERSION_ID;
/**
* @internal
@ -118,7 +119,8 @@ class FileStorageCacheProvider
private function getCacheHash(string $file_path, string $file_contents): string
{
return sha1(strtolower($file_path) . ' ' . $file_contents . $this->modified_timestamps);
$data = ($file_path ? $file_contents : '') . $this->modified_timestamps;
return PHP_VERSION_ID >= 80100 ? hash('xxh128', $data) : hash('md4', $data);
}
/**
@ -165,9 +167,15 @@ class FileStorageCacheProvider
mkdir($parser_cache_directory, 0777, true);
}
if (PHP_VERSION_ID >= 80100) {
$hash = hash('xxh128', $file_path);
} else {
$hash = hash('md4', $file_path);
}
return $parser_cache_directory
. DIRECTORY_SEPARATOR
. sha1($file_path)
. $hash
. ($this->config->use_igbinary ? '-igbinary' : '');
}
}

View File

@ -8,11 +8,12 @@ use function file_exists;
use function file_get_contents;
use function file_put_contents;
use function filemtime;
use function hash;
use function mkdir;
use function sha1;
use function touch;
use const DIRECTORY_SEPARATOR;
use const PHP_VERSION_ID;
/**
* Used to determine which files reference other files, necessary for using the --diff
@ -93,11 +94,15 @@ class ProjectCacheProvider
return true;
}
$sha1 = sha1($lockfile_contents);
if (PHP_VERSION_ID >= 80100) {
$hash = hash('xxh128', $lockfile_contents);
} else {
$hash = hash('md4', $lockfile_contents);
}
$changed = $sha1 !== $this->getComposerLockHash();
$changed = $hash !== $this->getComposerLockHash();
$this->composer_lock_hash = $sha1;
$this->composer_lock_hash = $hash;
return $changed;
}

View File

@ -895,6 +895,12 @@ class TypeParser
);
}
if (is_int($min_bound) && is_int($max_bound) && $min_bound > $max_bound) {
throw new TypeParseTreeException(
"Min bound can't be greater than max bound, int<$min_bound, $max_bound> given"
);
}
return new TIntRange($min_bound, $max_bound);
}

View File

@ -195,9 +195,10 @@ function array_search($needle, array $haystack, bool $strict = false)
/**
* @psalm-template T
* @psalm-template TArray as array<T>
*
* @param T[] $array
* @param-out list<T> $array
* @param TArray $array
* @param-out (TArray is non-empty-array ? non-empty-list<T> : list<T>) $array
*/
function shuffle(array &$array): bool
{
@ -205,9 +206,10 @@ function shuffle(array &$array): bool
/**
* @psalm-template T
* @psalm-template TArray as array<T>
*
* @param T[] $array
* @param-out list<T> $array
* @param TArray $array
* @param-out (TArray is non-empty-array ? non-empty-list<T> : list<T>) $array
*/
function sort(array &$array, int $flags = SORT_REGULAR): bool
{
@ -215,9 +217,10 @@ function sort(array &$array, int $flags = SORT_REGULAR): bool
/**
* @psalm-template T
* @psalm-template TArray as array<T>
*
* @param T[] $array
* @param-out list<T> $array
* @param TArray $array
* @param-out (TArray is non-empty-array ? non-empty-list<T> : list<T>) $array
*/
function rsort(array &$array, int $flags = SORT_REGULAR): bool
{
@ -225,10 +228,11 @@ function rsort(array &$array, int $flags = SORT_REGULAR): bool
/**
* @psalm-template T
* @psalm-template TArray as array<T>
*
* @param T[] $array
* @param TArray $array
* @param callable(T,T):int $callback
* @param-out list<T> $array
* @param-out (TArray is non-empty-array ? non-empty-list<T> : list<T>) $array
*/
function usort(array &$array, callable $callback): bool
{
@ -237,10 +241,11 @@ function usort(array &$array, callable $callback): bool
/**
* @psalm-template TKey
* @psalm-template T
* @psalm-template TArray as array<TKey,T>
*
* @param array<TKey,T> $array
* @param TArray $array
* @param callable(T,T):int $callback
* @param-out array<TKey,T> $array
* @param-out (TArray is non-empty-array ? non-empty-array<TKey,T> : array<TKey,T>) $array
*/
function uasort(array &$array, callable $callback): bool
{
@ -249,10 +254,11 @@ function uasort(array &$array, callable $callback): bool
/**
* @psalm-template TKey
* @psalm-template T
* @psalm-template TArray as array<TKey,T>
*
* @param array<TKey,T> $array
* @param TArray $array
* @param callable(TKey,TKey):int $callback
* @param-out array<TKey,T> $array
* @param-out (TArray is non-empty-array ? non-empty-array<TKey,T> : array<TKey,T>) $array
*/
function uksort(array &$array, callable $callback): bool
{
@ -585,8 +591,13 @@ function rtrim(string $string, string $characters = " \t\n\r\0\x0B") : string {}
* : non-empty-string
* )
* : string)
* : ($array is non-empty-array
* ? ($array is array<non-empty-literal-string|non-empty-string>
* ? ($array is array<non-empty-literal-string> ? non-empty-literal-string : non-empty-string)
* : string
* )
* : string)
* )
*
* @psalm-flow ($separator) -> return
* @psalm-flow ($array) -(array-fetch)-> return
@ -607,8 +618,13 @@ function implode($separator, array $array = []) : string {}
* : non-empty-string
* )
* : string)
* : ($array is non-empty-array
* ? ($array is array<non-empty-literal-string|non-empty-string>
* ? ($array is array<non-empty-literal-string> ? non-empty-literal-string : non-empty-string)
* : string
* )
* : string)
* )
*
* @psalm-flow ($separator) -> return
* @psalm-flow ($array) -(array-fetch)-> return
@ -1336,6 +1352,7 @@ function get_defined_constants(bool $categorize = false): array {}
/**
* @param mixed $object_or_class
* @param class-string $class
* @param bool $allow_string
* @return ($allow_string is false ? ($object_or_class is object ? bool : false) : bool)
*/
function is_a($object_or_class, string $class, $allow_string = false): bool{}

92
stubs/ext-apcu.phpstub Normal file
View File

@ -0,0 +1,92 @@
<?php
define('APC_LIST_ACTIVE', 1);
define('APC_LIST_DELETED', 2);
define('APC_ITER_TYPE', 1);
define('APC_ITER_KEY', 2);
define('APC_ITER_FILENAME', 4);
define('APC_ITER_DEVICE', 8);
define('APC_ITER_INODE', 16);
define('APC_ITER_VALUE', 32);
define('APC_ITER_MD5', 64);
define('APC_ITER_NUM_HITS', 128);
define('APC_ITER_MTIME', 256);
define('APC_ITER_CTIME', 512);
define('APC_ITER_DTIME', 1024);
define('APC_ITER_ATIME', 2048);
define('APC_ITER_REFCOUNT', 4096);
define('APC_ITER_MEM_SIZE', 8192);
define('APC_ITER_TTL', 16384);
define('APC_ITER_NONE', 0);
define('APC_ITER_ALL', -1);
class APCUIterator implements Iterator
{
/**
* @param array<string>|null|string $search
* @param int $format
* @param int $chunk_size
* @param int $list
*
* @return void
*/
public function __construct($search, $format = APC_ITER_ALL, $chunk_size = 100, $list = APC_LIST_ACTIVE)
{
}
/**
* @return void
*/
public function rewind()
{
}
/**
* @return void
*/
public function next()
{
}
/**
* @return bool
*/
public function valid()
{
}
/**
* @return string
*/
public function key()
{
}
/**
* @return mixed
*/
public function current()
{
}
/**
* @return int
*/
public function getTotalHits()
{
}
/**
* @return int
*/
public function getTotalSize()
{
}
/**
* @return int
*/
public function getTotalCount()
{
}
}

View File

@ -116,10 +116,13 @@ class ArgTest extends TestCase
ksort($a);
$b = ["b" => 5, "a" => 8];
sort($b);
$c = [];
sort($c);
',
'assertions' => [
'$a' => 'array{a: int, b: int}',
'$b' => 'list<int>',
'$b' => 'non-empty-list<int>',
'$c' => 'list<empty>',
],
],
'arrayModificationFunctions' => [
@ -690,6 +693,19 @@ class ArgTest extends TestCase
',
'error_message' => 'ArgumentTypeCoercion',
],
'objectRedundantCast' => [
'<?php
function makeObj(): object {
return (object)["a" => 42];
}
function takesObject(object $_o): void {}
takesObject((object)makeObj()); // expected: RedundantCast
',
'error_message' => 'RedundantCast',
],
'MissingMandatoryParamWithNamedParams' => [
'code' => '<?php
class User

View File

@ -666,28 +666,44 @@ class ArrayFunctionCallTest extends TestCase
],
'uasort' => [
'code' => '<?php
function foo (int $a, int $b): int {
return $a > $b ? 1 : -1;
}
$manifest = ["a" => 1, "b" => 2];
uasort(
$manifest,
function (int $a, int $b) {
return $a > $b ? 1 : -1;
}
);',
"foo"
);
$emptyManifest = [];
uasort(
$emptyManifest,
"foo"
);
',
'assertions' => [
'$manifest' => 'array<string, int>'
'$manifest' => 'non-empty-array<string, int>',
'$emptyManifest' => 'array<empty, empty>',
],
],
'uksort' => [
'code' => '<?php
function foo (string $a, string $b): int {
return $a <=> $b;
}
$array = ["b" => 1, "a" => 2];
uksort(
$array,
function (string $a, string $b) {
return $a <=> $b;
}
"foo"
);
$emptyArray = [];
uksort(
$emptyArray,
"foo"
);',
'assertions' => [
'$array' => 'array<string, int>',
'$array' => 'non-empty-array<string, int>',
'$emptyArray' => 'array<empty, empty>',
],
],
'arrayMergeTKeyedArray' => [
@ -1040,6 +1056,31 @@ class ArrayFunctionCallTest extends TestCase
'$b===' => 'non-empty-literal-string',
]
],
'implodeArrayOfNonEmptyStringAndEmptyString' => [
'code' => '<?php
class Foo {
const DIR = __DIR__;
}
$l = ["a", "b"];
$k = [Foo::DIR];
$a = implode("", $l);
$b = implode("", $k);',
'assertions' => [
'$a===' => 'non-empty-literal-string',
'$b===' => 'non-empty-string',
]
],
'implodeEmptyArrayAndString' => [
'code' => '<?php
$l = [""];
$k = [];
$a = implode("", $l);
$b = implode("", $k);',
'assertions' => [
'$a===' => 'string',
'$b===' => 'string',
]
],
'key' => [
'code' => '<?php
$a = ["one" => 1, "two" => 3];
@ -1762,33 +1803,46 @@ class ArrayFunctionCallTest extends TestCase
'shuffle' => [
'code' => '<?php
$array = ["foo" => 123, "bar" => 456];
shuffle($array);',
shuffle($array);
$emptyArray = [];
shuffle($emptyArray);',
'assertions' => [
'$array' => 'list<int>',
'$array' => 'non-empty-list<int>',
'$emptyArray' => 'list<empty>',
],
],
'sort' => [
'code' => '<?php
$array = ["foo" => 123, "bar" => 456];
sort($array);',
sort($array);
$emptyArray = [];
sort($emptyArray);',
'assertions' => [
'$array' => 'list<int>',
'$array' => 'non-empty-list<int>',
'$emptyArray' => 'list<empty>',
],
],
'rsort' => [
'code' => '<?php
$array = ["foo" => 123, "bar" => 456];
sort($array);',
rsort($array);
$emptyArray = [];
rsort($emptyArray);',
'assertions' => [
'$array' => 'list<int>',
'$array' => 'non-empty-list<int>',
'$emptyArray' => 'list<empty>',
],
],
'usort' => [
'code' => '<?php
function baz (int $a, int $b): int { return $a <=> $b; }
$array = ["foo" => 123, "bar" => 456];
usort($array, function (int $a, int $b) { return $a <=> $b; });',
usort($array, "baz");
$emptyArray = [];
usort($emptyArray, "baz");',
'assertions' => [
'$array' => 'list<int>',
'$array' => 'non-empty-list<int>',
'$emptyArray' => 'list<empty>',
],
],
'closureParamConstraintsMet' => [

View File

@ -90,10 +90,14 @@ class GetObjectVarsTest extends TestCase
'assertions' => [],
];
yield 'propertiesOfCastScalar' => [
'code' => '<?php $ret = get_object_vars((object)true);',
'assertions' => ['$ret' => 'array{scalar: true}'],
];
yield 'propertiesOfPOPO' => [
// todo: fix object cast so that it results in `object{a:1}` instead
'code' => '<?php $ret = get_object_vars((object)["a" => 1]);',
'assertions' => ['$ret' => 'array<string, mixed>'],
'assertions' => ['$ret' => 'array{a: int}'],
];
}
}

View File

@ -855,6 +855,33 @@ class ReturnTypeTest extends TestCase
'$res===' => 'iterable<int, numeric-string>',
],
],
'infersObjectShapeOfCastScalar' => [
'<?php
function returnsInt(): int {
return 1;
}
$obj = (object)returnsInt();
',
'assertions' => [
'$obj' => 'object{scalar:int}',
],
],
'infersObjectShapeOfCastArray' => [
'<?php
/**
* @return array{a:1}
*/
function returnsArray(): array {
return ["a" => 1];
}
$obj = (object)returnsArray();
',
'assertions' => [
'$obj' => 'object{a:int}',
],
],
'mixedAssignmentWithUnderscore' => [
'code' => '<?php
$gen = (function (): Generator {
@ -1542,6 +1569,17 @@ class ReturnTypeTest extends TestCase
',
'error_message' => 'LessSpecificReturnStatement',
],
'objectCastFromArrayWithMissingKey' => [
'<?php
/** @return object{status: string} */
function foo(): object {
return (object) [
"notstatus" => "failed",
];
}
',
'error_message' => 'InvalidReturnStatement',
],
'lessSpecificImplementedReturnTypeFromTemplatedTraitMethod' => [
'code' => '<?php
/** @template T */

View File

@ -1608,7 +1608,6 @@ class FunctionTemplateTest extends TestCase
],
'dontScreamForArithmeticsOnFloatTemplates' => [
'code' => '<?php
/**
* @template T of ?float
* @param T $p