From c6854cf5676ce3505e4fc0875c2aee1259f83044 Mon Sep 17 00:00:00 2001 From: asrar Date: Sat, 21 May 2022 17:39:51 +0200 Subject: [PATCH 001/178] Adds support for fixing missing throws doc block --- .../Analyzer/FunctionLikeAnalyzer.php | 17 +++++++++ .../Internal/Analyzer/ProjectAnalyzer.php | 1 + .../FunctionDocblockManipulator.php | 21 +++++++++++ .../FileManipulationTestCase.php | 1 + .../ThrowsBlockAdditionTest.php | 37 +++++++++++++++++++ 5 files changed, 77 insertions(+) create mode 100644 tests/FileManipulation/ThrowsBlockAdditionTest.php diff --git a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php index 6e3d216c61e..2fea7dd9ce8 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php @@ -706,6 +706,10 @@ public function analyze( } } + /** + * @var list + */ + $missingThrowsDocblockErrors = []; foreach ($statements_analyzer->getUncaughtThrows($context) as $possibly_thrown_exception => $codelocations) { $is_expected = false; @@ -719,6 +723,7 @@ public function analyze( } if (!$is_expected) { + $missingThrowsDocblockErrors[] = $possibly_thrown_exception; foreach ($codelocations as $codelocation) { // issues are suppressed in ThrowAnalyzer, CallAnalyzer, etc. IssueBuffer::maybeAdd( @@ -732,6 +737,18 @@ public function analyze( } } + if ( + $codebase->alter_code + && isset($project_analyzer->getIssuesToFix()['MissingThrowsDocblock']) + ) { + $manipulator = FunctionDocblockManipulator::getForFunction( + $project_analyzer, + $this->source->getFilePath(), + $this->function + ); + $manipulator->addThrowsDocblock($missingThrowsDocblockErrors); + } + if ($codebase->taint_flow_graph && $this->function instanceof ClassMethod && $cased_method_id diff --git a/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php b/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php index 4e99005e19e..7275069e174 100644 --- a/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php @@ -1322,6 +1322,7 @@ public function setIssuesToFix(array $issues): void $supported_issues_to_fix[] = 'MissingImmutableAnnotation'; $supported_issues_to_fix[] = 'MissingPureAnnotation'; + $supported_issues_to_fix[] = 'MissingThrowsDocblock'; $unsupportedIssues = array_diff(array_keys($issues), $supported_issues_to_fix); diff --git a/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php b/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php index 147ba4e7ccf..d2d9da9a060 100644 --- a/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php +++ b/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php @@ -96,6 +96,9 @@ class FunctionDocblockManipulator /** @var bool */ private $is_pure = false; + /** @var list */ + private $throwsExceptions = []; + /** * @param Closure|Function_|ClassMethod|ArrowFunction $stmt */ @@ -395,6 +398,16 @@ private function getDocblock(): string $modified_docblock = true; $parsed_docblock->tags['psalm-pure'] = ['']; } + if (\count($this->throwsExceptions) > 0) { + $modified_docblock = true; + $parsed_docblock->tags['throws'] = [ + \array_reduce( + $this->throwsExceptions, + fn(string $throwsClause, string $exception) => $throwsClause === '' ? $exception : $throwsClause.'|'.$exception, + '' + ) + ]; + } if ($this->new_phpdoc_return_type && $this->new_phpdoc_return_type !== $old_phpdoc_return_type) { @@ -528,6 +541,14 @@ public function makePure(): void $this->is_pure = true; } + /** + * @param list $exceptions + */ + public function addThrowsDocblock(array $exceptions): void + { + $this->throwsExceptions = $exceptions; + } + public static function clearCache(): void { self::$manipulators = []; diff --git a/tests/FileManipulation/FileManipulationTestCase.php b/tests/FileManipulation/FileManipulationTestCase.php index 61b9d7f724d..9ff5f542e83 100644 --- a/tests/FileManipulation/FileManipulationTestCase.php +++ b/tests/FileManipulation/FileManipulationTestCase.php @@ -86,6 +86,7 @@ public function testValidCode( $safe_types ); $this->project_analyzer->getCodebase()->allow_backwards_incompatible_changes = $allow_backwards_incompatible_changes; + $this->project_analyzer->getConfig()->check_for_throws_docblock = true; if (strpos(static::class, 'Unused') || strpos(static::class, 'Unnecessary')) { $this->project_analyzer->getCodebase()->reportUnusedCode(); diff --git a/tests/FileManipulation/ThrowsBlockAdditionTest.php b/tests/FileManipulation/ThrowsBlockAdditionTest.php new file mode 100644 index 00000000000..6214c7f0908 --- /dev/null +++ b/tests/FileManipulation/ThrowsBlockAdditionTest.php @@ -0,0 +1,37 @@ + + */ + public function providerValidCodeParse(): array + { + return [ + 'addThrowsAnnotationToFunction' => [ + ' Date: Sun, 22 May 2022 18:27:38 +0200 Subject: [PATCH 002/178] feat: fix ci + preserve existing throws --- .../Analyzer/FunctionLikeAnalyzer.php | 6 ++- .../FunctionDocblockManipulator.php | 23 +++++--- .../ThrowsBlockAdditionTest.php | 54 +++++++++++++++++++ 3 files changed, 73 insertions(+), 10 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php index 2fea7dd9ce8..cc5814a25e7 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php @@ -65,6 +65,8 @@ use function array_merge; use function array_search; use function array_values; +use function assert; +use function class_exists; use function count; use function end; use function in_array; @@ -723,6 +725,7 @@ public function analyze( } if (!$is_expected) { + assert(class_exists($possibly_thrown_exception)); $missingThrowsDocblockErrors[] = $possibly_thrown_exception; foreach ($codelocations as $codelocation) { // issues are suppressed in ThrowAnalyzer, CallAnalyzer, etc. @@ -737,8 +740,7 @@ public function analyze( } } - if ( - $codebase->alter_code + if ($codebase->alter_code && isset($project_analyzer->getIssuesToFix()['MissingThrowsDocblock']) ) { $manipulator = FunctionDocblockManipulator::getForFunction( diff --git a/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php b/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php index d2d9da9a060..bd2ab9ed6e7 100644 --- a/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php +++ b/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php @@ -14,7 +14,9 @@ use Psalm\Internal\Analyzer\ProjectAnalyzer; use Psalm\Internal\Scanner\ParsedDocblock; +use function array_key_exists; use function array_merge; +use function array_reduce; use function count; use function is_string; use function ltrim; @@ -398,15 +400,20 @@ private function getDocblock(): string $modified_docblock = true; $parsed_docblock->tags['psalm-pure'] = ['']; } - if (\count($this->throwsExceptions) > 0) { + if (count($this->throwsExceptions) > 0) { $modified_docblock = true; - $parsed_docblock->tags['throws'] = [ - \array_reduce( - $this->throwsExceptions, - fn(string $throwsClause, string $exception) => $throwsClause === '' ? $exception : $throwsClause.'|'.$exception, - '' - ) - ]; + $inferredThrowsClause = array_reduce( + $this->throwsExceptions, + function (string $throwsClause, string $exception) { + return $throwsClause === '' ? $exception : $throwsClause.'|'.$exception; + }, + '' + ); + if (array_key_exists('throws', $parsed_docblock->tags)) { + $parsed_docblock->tags['throws'][] = $inferredThrowsClause; + } else { + $parsed_docblock->tags['throws'] = [$inferredThrowsClause]; + } } diff --git a/tests/FileManipulation/ThrowsBlockAdditionTest.php b/tests/FileManipulation/ThrowsBlockAdditionTest.php index 6214c7f0908..93a15f2ee61 100644 --- a/tests/FileManipulation/ThrowsBlockAdditionTest.php +++ b/tests/FileManipulation/ThrowsBlockAdditionTest.php @@ -32,6 +32,60 @@ function foo(string $s): string { ['MissingThrowsDocblock'], true, ], + 'addMultipleThrowsAnnotationToFunction' => [ + ' [ + ' Date: Sun, 22 May 2022 18:38:18 +0200 Subject: [PATCH 003/178] chore: add another test --- .../ThrowsBlockAdditionTest.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/FileManipulation/ThrowsBlockAdditionTest.php b/tests/FileManipulation/ThrowsBlockAdditionTest.php index 93a15f2ee61..ae9eabf415d 100644 --- a/tests/FileManipulation/ThrowsBlockAdditionTest.php +++ b/tests/FileManipulation/ThrowsBlockAdditionTest.php @@ -86,6 +86,38 @@ function foo(string $s): string { ['MissingThrowsDocblock'], true, ], + 'doesNotAddDuplicateThrows' => [ + ' Date: Mon, 23 May 2022 19:45:33 +0200 Subject: [PATCH 004/178] refactor: use list --- src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php | 4 ---- .../Internal/FileManipulation/FunctionDocblockManipulator.php | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php index cc5814a25e7..faf22a70627 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php @@ -708,9 +708,6 @@ public function analyze( } } - /** - * @var list - */ $missingThrowsDocblockErrors = []; foreach ($statements_analyzer->getUncaughtThrows($context) as $possibly_thrown_exception => $codelocations) { $is_expected = false; @@ -725,7 +722,6 @@ public function analyze( } if (!$is_expected) { - assert(class_exists($possibly_thrown_exception)); $missingThrowsDocblockErrors[] = $possibly_thrown_exception; foreach ($codelocations as $codelocation) { // issues are suppressed in ThrowAnalyzer, CallAnalyzer, etc. diff --git a/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php b/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php index bd2ab9ed6e7..ca652333ea8 100644 --- a/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php +++ b/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php @@ -98,7 +98,7 @@ class FunctionDocblockManipulator /** @var bool */ private $is_pure = false; - /** @var list */ + /** @var list */ private $throwsExceptions = []; /** @@ -549,7 +549,7 @@ public function makePure(): void } /** - * @param list $exceptions + * @param list $exceptions */ public function addThrowsDocblock(array $exceptions): void { From ffe18296b0abab9477e858311fe459a475b45953 Mon Sep 17 00:00:00 2001 From: sergkash7 <55360924+sergkash7@users.noreply.github.com> Date: Tue, 21 Jun 2022 22:03:11 +0300 Subject: [PATCH 005/178] Update phpredis.phpstub --- stubs/phpredis.phpstub | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stubs/phpredis.phpstub b/stubs/phpredis.phpstub index 791a0cfb87c..0687320eff0 100644 --- a/stubs/phpredis.phpstub +++ b/stubs/phpredis.phpstub @@ -141,7 +141,7 @@ class Redis { public function geosearchstore(string $dst, string $src, array|string $position, array|int|float $shape, string $unit, array $options = []): array; - /** @return string|Redis */ + /** @return false|string|Redis */ public function get(string $key); public function getAuth(): mixed; From 7742d8a903d5daaadd23f278672dd102a4c0cc31 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Sun, 10 Jul 2022 09:23:13 +0200 Subject: [PATCH 006/178] use lock to fix race condition --- .../Internal/Provider/ParserCacheProvider.php | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Provider/ParserCacheProvider.php b/src/Psalm/Internal/Provider/ParserCacheProvider.php index 85c0c06c42c..755a064a807 100644 --- a/src/Psalm/Internal/Provider/ParserCacheProvider.php +++ b/src/Psalm/Internal/Provider/ParserCacheProvider.php @@ -8,9 +8,14 @@ use RuntimeException; use function error_log; +use function fclose; use function file_get_contents; use function file_put_contents; use function filemtime; +use function filesize; +use function flock; +use function fopen; +use function fread; use function gettype; use function igbinary_serialize; use function igbinary_unserialize; @@ -28,9 +33,12 @@ use function trigger_error; use function unlink; use function unserialize; +use function usleep; use const DIRECTORY_SEPARATOR; use const E_USER_ERROR; +use const LOCK_EX; +use const LOCK_SH; use const SCANDIR_SORT_NONE; /** @@ -184,7 +192,28 @@ private function getExistingFileContentHashes(): array $file_hashes_path = $root_cache_directory . DIRECTORY_SEPARATOR . self::FILE_HASHES; if ($root_cache_directory && is_readable($file_hashes_path)) { - $hashes_encoded = (string) file_get_contents($file_hashes_path); + $fp = fopen($file_hashes_path, 'r'); + $max_wait_cycles = 5; + $has_lock = false; + while ($max_wait_cycles > 0) { + if (flock($fp, LOCK_SH)) { + $has_lock = true; + break; + } + $max_wait_cycles--; + usleep(50000); + } + + if (!$has_lock) { + fclose($fp); + error_log('Could not acquire lock for content hashes file'); + $this->existing_file_content_hashes = []; + + return []; + } + + $hashes_encoded = fread($fp, filesize($file_hashes_path)); + fclose($fp); if (!$hashes_encoded) { error_log('Unexpected value when loading from file content hashes'); @@ -281,7 +310,8 @@ public function saveFileContentHashes(): void file_put_contents( $file_hashes_path, - json_encode($file_content_hashes) + json_encode($file_content_hashes), + LOCK_EX ); } From a77f6fca12df7260c123d5b11c597de7fcdaa83a Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Sun, 10 Jul 2022 12:01:45 +0200 Subject: [PATCH 007/178] use error_log --- src/Psalm/Config.php | 5 ++--- src/Psalm/Internal/Provider/ParserCacheProvider.php | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index 5748f15953e..08bc9604f2e 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -54,6 +54,7 @@ use function class_exists; use function count; use function dirname; +use function error_log; use function explode; use function extension_loaded; use function file_exists; @@ -94,12 +95,10 @@ use function substr; use function substr_count; use function sys_get_temp_dir; -use function trigger_error; use function unlink; use function version_compare; use const DIRECTORY_SEPARATOR; -use const E_USER_ERROR; use const GLOB_NOSORT; use const LIBXML_ERR_ERROR; use const LIBXML_ERR_FATAL; @@ -1016,7 +1015,7 @@ private static function fromXmlAndPaths( } if (is_dir($config->cache_directory) === false && @mkdir($config->cache_directory, 0777, true) === false) { - trigger_error('Could not create cache directory: ' . $config->cache_directory, E_USER_ERROR); + error_log('Could not create cache directory: ' . $config->cache_directory); } if ($cwd) { diff --git a/src/Psalm/Internal/Provider/ParserCacheProvider.php b/src/Psalm/Internal/Provider/ParserCacheProvider.php index 755a064a807..893844e2eee 100644 --- a/src/Psalm/Internal/Provider/ParserCacheProvider.php +++ b/src/Psalm/Internal/Provider/ParserCacheProvider.php @@ -30,13 +30,11 @@ use function scandir; use function serialize; use function touch; -use function trigger_error; use function unlink; use function unserialize; use function usleep; use const DIRECTORY_SEPARATOR; -use const E_USER_ERROR; use const LOCK_EX; use const LOCK_SH; use const SCANDIR_SORT_NONE; @@ -385,7 +383,7 @@ private function createCacheDirectory(string $parser_cache_directory): void } catch (RuntimeException $e) { // Race condition (#4483) if (!is_dir($parser_cache_directory)) { - trigger_error('Could not create parser cache directory: ' . $parser_cache_directory, E_USER_ERROR); + error_log('Could not create parser cache directory: ' . $parser_cache_directory); } } } From 3b76ac85dc7f67655a855003751d25d2742cbf73 Mon Sep 17 00:00:00 2001 From: Jack Worman Date: Sun, 10 Jul 2022 17:20:45 -0500 Subject: [PATCH 008/178] Count Report Format --- .../Internal/Analyzer/ProjectAnalyzer.php | 1 + src/Psalm/IssueBuffer.php | 9 ++++- src/Psalm/Report.php | 19 +-------- src/Psalm/Report/CountReport.php | 39 +++++++++++++++++++ src/Psalm/Report/ReportOptions.php | 2 +- tests/ReportOutputTest.php | 20 ++++++++++ 6 files changed, 70 insertions(+), 20 deletions(-) create mode 100644 src/Psalm/Report/CountReport.php diff --git a/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php b/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php index 4e99005e19e..44cec8c4246 100644 --- a/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php @@ -397,6 +397,7 @@ public static function getFileReportOptions(array $report_file_paths, bool $show '.pylint' => Report::TYPE_PYLINT, '.console' => Report::TYPE_CONSOLE, '.sarif' => Report::TYPE_SARIF, + 'count.txt' => Report::TYPE_COUNT, ]; foreach ($report_file_paths as $report_file_path) { diff --git a/src/Psalm/IssueBuffer.php b/src/Psalm/IssueBuffer.php index 0fea8f035fc..47bde92fccd 100644 --- a/src/Psalm/IssueBuffer.php +++ b/src/Psalm/IssueBuffer.php @@ -16,11 +16,11 @@ use Psalm\Issue\TaintedInput; use Psalm\Issue\UnusedPsalmSuppress; use Psalm\Plugin\EventHandler\Event\AfterAnalysisEvent; -use Psalm\Report; use Psalm\Report\CheckstyleReport; use Psalm\Report\CodeClimateReport; use Psalm\Report\CompactReport; use Psalm\Report\ConsoleReport; +use Psalm\Report\CountReport; use Psalm\Report\EmacsReport; use Psalm\Report\GithubActionsReport; use Psalm\Report\JsonReport; @@ -908,6 +908,13 @@ public static function getOutput( case Report::TYPE_CODECLIMATE: $output = new CodeClimateReport($normalized_data, self::$fixable_issue_counts, $report_options); break; + + case Report::TYPE_COUNT: + $output = new CountReport($normalized_data, self::$fixable_issue_counts, $report_options); + break; + + default: + throw new RuntimeException('Unexpected report format: ' . $report_options->format); } return $output->create(); diff --git a/src/Psalm/Report.php b/src/Psalm/Report.php index 6ad69c849fc..97604a799dd 100644 --- a/src/Psalm/Report.php +++ b/src/Psalm/Report.php @@ -28,24 +28,7 @@ abstract class Report public const TYPE_PHP_STORM = 'phpstorm'; public const TYPE_SARIF = 'sarif'; public const TYPE_CODECLIMATE = 'codeclimate'; - - public const SUPPORTED_OUTPUT_TYPES = [ - self::TYPE_COMPACT, - self::TYPE_CONSOLE, - self::TYPE_PYLINT, - self::TYPE_JSON, - self::TYPE_JSON_SUMMARY, - self::TYPE_SONARQUBE, - self::TYPE_EMACS, - self::TYPE_XML, - self::TYPE_JUNIT, - self::TYPE_CHECKSTYLE, - self::TYPE_TEXT, - self::TYPE_GITHUB_ACTIONS, - self::TYPE_PHP_STORM, - self::TYPE_SARIF, - self::TYPE_CODECLIMATE, - ]; + public const TYPE_COUNT = 'count'; /** * @var array diff --git a/src/Psalm/Report/CountReport.php b/src/Psalm/Report/CountReport.php new file mode 100644 index 00000000000..b044d851651 --- /dev/null +++ b/src/Psalm/Report/CountReport.php @@ -0,0 +1,39 @@ +issues_data as $issue_data) { + if (array_key_exists($issue_data->type, $issue_type_counts)) { + $issue_type_counts[$issue_data->type]++; + } else { + $issue_type_counts[$issue_data->type] = 1; + } + } + uksort($issue_type_counts, function (string $a, string $b) use ($issue_type_counts): int { + $cmp_result = $issue_type_counts[$a] <=> $issue_type_counts[$b]; + if ($cmp_result === 0) { + return $a <=> $b; + } else { + return $cmp_result; + } + }); + + $output = ''; + foreach ($issue_type_counts as $issue_type => $count) { + $output .= "{$issue_type}: {$count}\n"; + } + return $output; + } +} diff --git a/src/Psalm/Report/ReportOptions.php b/src/Psalm/Report/ReportOptions.php index 3ade03bd083..3f9143536b2 100644 --- a/src/Psalm/Report/ReportOptions.php +++ b/src/Psalm/Report/ReportOptions.php @@ -22,7 +22,7 @@ class ReportOptions public $show_info = true; /** - * @var value-of + * @var Report::TYPE_* */ public $format = Report::TYPE_CONSOLE; diff --git a/tests/ReportOutputTest.php b/tests/ReportOutputTest.php index 5fd7b59bc47..895aef8271d 100644 --- a/tests/ReportOutputTest.php +++ b/tests/ReportOutputTest.php @@ -1269,6 +1269,26 @@ public function testGithubActionsOutput(): void ); } + public function testCountOutput(): void + { + $this->analyzeFileForReport(); + + $report_options = new ReportOptions(); + $report_options->format = Report::TYPE_COUNT; + $expected_output = <<<'EOF' +MixedInferredReturnType: 1 +MixedReturnStatement: 1 +PossiblyUndefinedGlobalVariable: 1 +UndefinedConstant: 1 +UndefinedVariable: 1 + +EOF; + $this->assertSame( + $expected_output, + IssueBuffer::getOutput(IssueBuffer::getIssuesData(), $report_options) + ); + } + public function testEmptyReportIfNotError(): void { $this->addFile( From bcf3c5153c10830f6b71a3103f46e61d0f26b366 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Mon, 4 Jul 2022 01:10:49 +0200 Subject: [PATCH 009/178] Fix GEOSGeometry stubs with default values --- dictionaries/CallMap.php | 4 ++-- dictionaries/CallMap_historical.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 10f9d7189c7..4ea825e6c59 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -3633,7 +3633,7 @@ 'GEOSGeometry::__toString' => ['string'], 'GEOSGeometry::project' => ['float', 'other'=>'GEOSGeometry', 'normalized'=>'bool'], 'GEOSGeometry::interpolate' => ['GEOSGeometry', 'dist'=>'float', 'normalized'=>'bool'], -'GEOSGeometry::buffer' => ['GEOSGeometry', 'dist'=>'float', 'styleArray'=>'array'], +'GEOSGeometry::buffer' => ['GEOSGeometry', 'dist'=>'float', 'styleArray='=>'array'], 'GEOSGeometry::offsetCurve' => ['GEOSGeometry', 'dist'=>'float', 'styleArray'=>'array'], 'GEOSGeometry::envelope' => ['GEOSGeometry'], 'GEOSGeometry::intersection' => ['GEOSGeometry', 'geom'=>'GEOSGeometry'], @@ -3646,7 +3646,7 @@ 'GEOSGeometry::centroid' => ['GEOSGeometry'], 'GEOSGeometry::relate' => ['string|bool', 'otherGeom'=>'GEOSGeometry', 'pattern'=>'string'], 'GEOSGeometry::relateBoundaryNodeRule' => ['string', 'otherGeom'=>'GEOSGeometry', 'rule'=>'int'], -'GEOSGeometry::simplify' => ['GEOSGeometry', 'tolerance'=>'float', 'preserveTopology'=>'bool'], +'GEOSGeometry::simplify' => ['GEOSGeometry', 'tolerance'=>'float', 'preserveTopology='=>'bool'], 'GEOSGeometry::normalize' => ['GEOSGeometry'], 'GEOSGeometry::extractUniquePoints' => ['GEOSGeometry'], 'GEOSGeometry::disjoint' => ['bool', 'geom'=>'GEOSGeometry'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index ffff495f1fc..3f2b43e60a1 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1781,7 +1781,7 @@ 'GEOSGeometry::__toString' => ['string'], 'GEOSGeometry::area' => ['float'], 'GEOSGeometry::boundary' => ['GEOSGeometry'], - 'GEOSGeometry::buffer' => ['GEOSGeometry', 'dist'=>'float', 'styleArray'=>'array'], + 'GEOSGeometry::buffer' => ['GEOSGeometry', 'dist'=>'float', 'styleArray='=>'array'], 'GEOSGeometry::centroid' => ['GEOSGeometry'], 'GEOSGeometry::checkValidity' => ['array{valid: bool, reason?: string, location?: GEOSGeometry}'], 'GEOSGeometry::contains' => ['bool', 'geom'=>'GEOSGeometry'], @@ -1830,7 +1830,7 @@ 'GEOSGeometry::relate' => ['string|bool', 'otherGeom'=>'GEOSGeometry', 'pattern'=>'string'], 'GEOSGeometry::relateBoundaryNodeRule' => ['string', 'otherGeom'=>'GEOSGeometry', 'rule'=>'int'], 'GEOSGeometry::setSRID' => ['void', 'srid'=>'int'], - 'GEOSGeometry::simplify' => ['GEOSGeometry', 'tolerance'=>'float', 'preserveTopology'=>'bool'], + 'GEOSGeometry::simplify' => ['GEOSGeometry', 'tolerance'=>'float', 'preserveTopology='=>'bool'], 'GEOSGeometry::snapTo' => ['GEOSGeometry', 'geom'=>'GEOSGeometry', 'tolerance'=>'float'], 'GEOSGeometry::startPoint' => ['GEOSGeometry'], 'GEOSGeometry::symDifference' => ['GEOSGeometry', 'geom'=>'GEOSGeometry'], From 470885e4f1c366d769660471a60d25f64902cd87 Mon Sep 17 00:00:00 2001 From: someniatko Date: Tue, 12 Jul 2022 13:43:53 +0300 Subject: [PATCH 010/178] #8200 - improve inferring the "final" `static` type when calling static methods inside a different class differentiate between `static` defined in a class which CALLS a given static method, and `static` defined in the method which IS CALLED. --- .../ExistingAtomicStaticCallAnalyzer.php | 34 +++++++++++ tests/Template/Issue8200Test.php | 58 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 tests/Template/Issue8200Test.php diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php index 7855200e95e..d8af9168d55 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php @@ -551,6 +551,18 @@ private static function getMethodReturnType( ) { $static_type = $context->self; $context_final = $codebase->classlike_storage_provider->get($context->self)->final; + } elseif ($context->calling_method_id !== null) { + $self_method_return = $codebase->methods->getMethodReturnType( + MethodIdentifier::fromMethodIdReference($context->calling_method_id), + $context->self + ); + // differentiate between these cases: + // 1. "static" in return type comes from return type of the + // method CALLING the currently analyzed static method - use $context->self. + // 2. "static" comes from the CALLED static method - use $fq_class_name. + $static_type = $self_method_return !== null && self::hasStaticInType($self_method_return) + ? $context->self + : $fq_class_name; } else { $static_type = $fq_class_name; } @@ -613,4 +625,26 @@ private static function getMethodReturnType( return $return_type_candidate; } + + /** + * Dumb way to determine whether a type contains "static" somewhere inside. + */ + private static function hasStaticInType(Union $union_type): bool + { + foreach ($union_type->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof Atomic\TGenericObject) { + foreach ($atomic_type->type_params as $type_param) { + if (self::hasStaticInType($type_param)) { + return true; + } + } + } elseif ($atomic_type instanceof TNamedObject) { + if ($atomic_type->value === 'static') { + return true; + } + } + } + + return false; + } } diff --git a/tests/Template/Issue8200Test.php b/tests/Template/Issue8200Test.php new file mode 100644 index 00000000000..a20b5fa7507 --- /dev/null +++ b/tests/Template/Issue8200Test.php @@ -0,0 +1,58 @@ +,error_levels?:string[]}> + */ + public function providerValidCodeParse(): iterable + { + return [ + 'return TemplatedClass' => [ + ' + * + * @psalm-pure + */ + public static function just($value): self + { + return new self($value); + } + } + + abstract class Test + { + final private function __construct() {} + + /** @return Maybe */ + final public static function create(): Maybe + { + return Maybe::just(new static()); + } + }', + ], + ]; + } +} From 3a5054018bcc3a0708e53883fa0e4935a4ff6dad Mon Sep 17 00:00:00 2001 From: someniatko Date: Tue, 12 Jul 2022 21:00:19 +0300 Subject: [PATCH 011/178] #8200 - generalize ExistingAtomicStaticCallAnalyzer::hasStaticInType() for non-object cases --- .../ExistingAtomicStaticCallAnalyzer.php | 24 ++++++++------- tests/Template/Issue8200Test.php | 29 +++++++++++++++++++ 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php index d8af9168d55..4d13f11bd32 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php @@ -35,6 +35,7 @@ use Psalm\Type\Union; use function array_map; +use function assert; use function count; use function explode; use function in_array; @@ -629,17 +630,20 @@ private static function getMethodReturnType( /** * Dumb way to determine whether a type contains "static" somewhere inside. */ - private static function hasStaticInType(Union $union_type): bool + private static function hasStaticInType(Type\TypeNode $type): bool { - foreach ($union_type->getAtomicTypes() as $atomic_type) { - if ($atomic_type instanceof Atomic\TGenericObject) { - foreach ($atomic_type->type_params as $type_param) { - if (self::hasStaticInType($type_param)) { - return true; - } - } - } elseif ($atomic_type instanceof TNamedObject) { - if ($atomic_type->value === 'static') { + assert($type instanceof Atomic || $type instanceof Union); + $union_parts = $type instanceof Union + ? $type->getAtomicTypes() + : [ $type ]; + + foreach ($union_parts as $atomic_type) { + if ($atomic_type instanceof TNamedObject && $atomic_type->value === 'static') { + return true; + } + + foreach ($atomic_type->getChildNodes() as $child_type) { + if (self::hasStaticInType($child_type)) { return true; } } diff --git a/tests/Template/Issue8200Test.php b/tests/Template/Issue8200Test.php index a20b5fa7507..2e04bc1a51b 100644 --- a/tests/Template/Issue8200Test.php +++ b/tests/Template/Issue8200Test.php @@ -53,6 +53,35 @@ final public static function create(): Maybe } }', ], + 'return list' => [ + ' + * + * @psalm-pure + */ + public static function mklist($value): array + { + return [ $value ]; + } + } + + abstract class Test + { + final private function __construct() {} + + /** @return list */ + final public static function create(): array + { + return Lister::mklist(new static()); + } + }', + ] ]; } } From b3e673d7ec3dd2e07393ae08ee4e37a57f46c0d0 Mon Sep 17 00:00:00 2001 From: someniatko Date: Tue, 12 Jul 2022 21:17:10 +0300 Subject: [PATCH 012/178] #8200 - flip logic of determining "source" of `static` type in ExistingAtomicStaticCallAnalyzer::getMethodReturnType() --- .../ExistingAtomicStaticCallAnalyzer.php | 14 +++---- tests/Template/Issue8200Test.php | 38 +++++++++++++++++++ 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php index 4d13f11bd32..21484782e0b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php @@ -553,17 +553,13 @@ private static function getMethodReturnType( $static_type = $context->self; $context_final = $codebase->classlike_storage_provider->get($context->self)->final; } elseif ($context->calling_method_id !== null) { - $self_method_return = $codebase->methods->getMethodReturnType( - MethodIdentifier::fromMethodIdReference($context->calling_method_id), - $context->self - ); // differentiate between these cases: - // 1. "static" in return type comes from return type of the + // 1. "static" comes from the CALLED static method - use $fq_class_name. + // 2. "static" in return type comes from return type of the // method CALLING the currently analyzed static method - use $context->self. - // 2. "static" comes from the CALLED static method - use $fq_class_name. - $static_type = $self_method_return !== null && self::hasStaticInType($self_method_return) - ? $context->self - : $fq_class_name; + $static_type = self::hasStaticInType($return_type_candidate) + ? $fq_class_name + : $context->self; } else { $static_type = $fq_class_name; } diff --git a/tests/Template/Issue8200Test.php b/tests/Template/Issue8200Test.php index 2e04bc1a51b..202466b9019 100644 --- a/tests/Template/Issue8200Test.php +++ b/tests/Template/Issue8200Test.php @@ -81,6 +81,44 @@ final public static function create(): array return Lister::mklist(new static()); } }', + ], + 'use TemplatedClass as an intermediate variable inside a method' => [ + ' + * + * @psalm-pure + */ + public static function just($value): self + { + return new self($value); + } + } + + abstract class Test + { + final private function __construct() {} + + final public static function create(): static + { + $maybe = Maybe::just(new static()); + return $maybe->value; + } + }', ] ]; } From ecbceb1d58736fe582949e2d2b7b198f529bc098 Mon Sep 17 00:00:00 2001 From: someniatko Date: Tue, 12 Jul 2022 21:38:23 +0300 Subject: [PATCH 013/178] #8200 - move Issue8200Test to ClassTemplateTest --- tests/Template/ClassTemplateTest.php | 105 ++++++++++++++++++++++ tests/Template/Issue8200Test.php | 125 --------------------------- 2 files changed, 105 insertions(+), 125 deletions(-) delete mode 100644 tests/Template/Issue8200Test.php diff --git a/tests/Template/ClassTemplateTest.php b/tests/Template/ClassTemplateTest.php index 62ee25a2380..9ea95e8d35b 100644 --- a/tests/Template/ClassTemplateTest.php +++ b/tests/Template/ClassTemplateTest.php @@ -4474,6 +4474,111 @@ class B {} ', 'error_message' => 'InvalidArgument', ], + 'return TemplatedClass' => [ + ' + * + * @psalm-pure + */ + public static function just($value): self + { + return new self($value); + } + } + + abstract class Test + { + final private function __construct() {} + + /** @return Maybe */ + final public static function create(): Maybe + { + return Maybe::just(new static()); + } + }', + ], + 'return list created in a static method of another class' => [ + ' + * + * @psalm-pure + */ + public static function mklist($value): array + { + return [ $value ]; + } + } + + abstract class Test + { + final private function __construct() {} + + /** @return list */ + final public static function create(): array + { + return Lister::mklist(new static()); + } + }', + ], + 'use TemplatedClass as an intermediate variable inside a method' => [ + ' + * + * @psalm-pure + */ + public static function just($value): self + { + return new self($value); + } + } + + abstract class Test + { + final private function __construct() {} + + final public static function create(): static + { + $maybe = Maybe::just(new static()); + return $maybe->value; + } + }', + ], ]; } } diff --git a/tests/Template/Issue8200Test.php b/tests/Template/Issue8200Test.php deleted file mode 100644 index 202466b9019..00000000000 --- a/tests/Template/Issue8200Test.php +++ /dev/null @@ -1,125 +0,0 @@ -,error_levels?:string[]}> - */ - public function providerValidCodeParse(): iterable - { - return [ - 'return TemplatedClass' => [ - ' - * - * @psalm-pure - */ - public static function just($value): self - { - return new self($value); - } - } - - abstract class Test - { - final private function __construct() {} - - /** @return Maybe */ - final public static function create(): Maybe - { - return Maybe::just(new static()); - } - }', - ], - 'return list' => [ - ' - * - * @psalm-pure - */ - public static function mklist($value): array - { - return [ $value ]; - } - } - - abstract class Test - { - final private function __construct() {} - - /** @return list */ - final public static function create(): array - { - return Lister::mklist(new static()); - } - }', - ], - 'use TemplatedClass as an intermediate variable inside a method' => [ - ' - * - * @psalm-pure - */ - public static function just($value): self - { - return new self($value); - } - } - - abstract class Test - { - final private function __construct() {} - - final public static function create(): static - { - $maybe = Maybe::just(new static()); - return $maybe->value; - } - }', - ] - ]; - } -} From 931b3bb18b3fa900cae5d51130ee8360ad4be58f Mon Sep 17 00:00:00 2001 From: someniatko Date: Tue, 12 Jul 2022 21:41:06 +0300 Subject: [PATCH 014/178] #8200 - simplify ExistingAtomicStaticCallAnalyzer::hasStaticType() --- .../ExistingAtomicStaticCallAnalyzer.php | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php index 21484782e0b..d7ac4ef16ff 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php @@ -35,7 +35,6 @@ use Psalm\Type\Union; use function array_map; -use function assert; use function count; use function explode; use function in_array; @@ -628,21 +627,14 @@ private static function getMethodReturnType( */ private static function hasStaticInType(Type\TypeNode $type): bool { - assert($type instanceof Atomic || $type instanceof Union); - $union_parts = $type instanceof Union - ? $type->getAtomicTypes() - : [ $type ]; + if ($type instanceof TNamedObject && $type->value === 'static') { + return true; + } - foreach ($union_parts as $atomic_type) { - if ($atomic_type instanceof TNamedObject && $atomic_type->value === 'static') { + foreach ($type->getChildNodes() as $child_type) { + if (self::hasStaticInType($child_type)) { return true; } - - foreach ($atomic_type->getChildNodes() as $child_type) { - if (self::hasStaticInType($child_type)) { - return true; - } - } } return false; From 21a6dd909669f1faf85f9be8607e252183e91bb2 Mon Sep 17 00:00:00 2001 From: someniatko Date: Tue, 12 Jul 2022 21:53:14 +0300 Subject: [PATCH 015/178] #8200 - move tests to the correct provider ("valid" instead of "invalid") --- tests/Template/ClassTemplateTest.php | 210 +++++++++++++-------------- 1 file changed, 105 insertions(+), 105 deletions(-) diff --git a/tests/Template/ClassTemplateTest.php b/tests/Template/ClassTemplateTest.php index 9ea95e8d35b..73a0fa9d811 100644 --- a/tests/Template/ClassTemplateTest.php +++ b/tests/Template/ClassTemplateTest.php @@ -3694,6 +3694,111 @@ protected function setUp(): void } }', ], + 'return TemplatedClass' => [ + ' + * + * @psalm-pure + */ + public static function just($value): self + { + return new self($value); + } + } + + abstract class Test + { + final private function __construct() {} + + /** @return Maybe */ + final public static function create(): Maybe + { + return Maybe::just(new static()); + } + }', + ], + 'return list created in a static method of another class' => [ + ' + * + * @psalm-pure + */ + public static function mklist($value): array + { + return [ $value ]; + } + } + + abstract class Test + { + final private function __construct() {} + + /** @return list */ + final public static function create(): array + { + return Lister::mklist(new static()); + } + }', + ], + 'use TemplatedClass as an intermediate variable inside a method' => [ + ' + * + * @psalm-pure + */ + public static function just($value): self + { + return new self($value); + } + } + + abstract class Test + { + final private function __construct() {} + + final public static function create(): static + { + $maybe = Maybe::just(new static()); + return $maybe->value; + } + }', + ], ]; } @@ -4474,111 +4579,6 @@ class B {} ', 'error_message' => 'InvalidArgument', ], - 'return TemplatedClass' => [ - ' - * - * @psalm-pure - */ - public static function just($value): self - { - return new self($value); - } - } - - abstract class Test - { - final private function __construct() {} - - /** @return Maybe */ - final public static function create(): Maybe - { - return Maybe::just(new static()); - } - }', - ], - 'return list created in a static method of another class' => [ - ' - * - * @psalm-pure - */ - public static function mklist($value): array - { - return [ $value ]; - } - } - - abstract class Test - { - final private function __construct() {} - - /** @return list */ - final public static function create(): array - { - return Lister::mklist(new static()); - } - }', - ], - 'use TemplatedClass as an intermediate variable inside a method' => [ - ' - * - * @psalm-pure - */ - public static function just($value): self - { - return new self($value); - } - } - - abstract class Test - { - final private function __construct() {} - - final public static function create(): static - { - $maybe = Maybe::just(new static()); - return $maybe->value; - } - }', - ], ]; } } From f28ac7377778e281c1b406251dd839f88ea4622e Mon Sep 17 00:00:00 2001 From: Corey Taylor Date: Fri, 8 Jul 2022 01:08:00 -0500 Subject: [PATCH 016/178] Fix nullable return types for CallMap functions --- dictionaries/CallMap.php | 56 +++++++++---------- dictionaries/CallMap_71_delta.php | 2 +- dictionaries/CallMap_80_delta.php | 8 +-- dictionaries/CallMap_81_delta.php | 4 +- dictionaries/CallMap_historical.php | 54 +++++++++--------- stubs/CoreGenericFunctions.phpstub | 2 +- .../Codebase/InternalCallMapHandlerTest.php | 16 ++++-- 7 files changed, 73 insertions(+), 69 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index ce0ffd4b209..c81fd16b8e4 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1066,7 +1066,7 @@ 'classObj::settext' => ['int', 'text'=>'string'], 'classObj::updateFromString' => ['int', 'snippet'=>'string'], 'clearstatcache' => ['void', 'clear_realpath_cache='=>'bool', 'filename='=>'string'], -'cli_get_process_title' => ['string'], +'cli_get_process_title' => ['?string'], 'cli_set_process_title' => ['bool', 'title'=>'string'], 'ClosedGeneratorException::__clone' => ['void'], 'ClosedGeneratorException::__toString' => ['string'], @@ -1106,7 +1106,7 @@ 'Collator::sortWithSortKeys' => ['bool', '&rw_arr'=>'array'], 'collator_asort' => ['bool', 'object'=>'collator', '&rw_array'=>'array', 'flags='=>'int'], 'collator_compare' => ['int', 'object'=>'collator', 'string1'=>'string', 'string2'=>'string'], -'collator_create' => ['Collator', 'locale'=>'string'], +'collator_create' => ['?Collator', 'locale'=>'string'], 'collator_get_attribute' => ['int|false', 'object'=>'collator', 'attribute'=>'int'], 'collator_get_error_code' => ['int', 'object'=>'collator'], 'collator_get_error_message' => ['string', 'object'=>'collator'], @@ -1678,7 +1678,7 @@ 'curl_multi_close' => ['void', 'multi_handle'=>'CurlMultiHandle'], 'curl_multi_errno' => ['int', 'multi_handle'=>'CurlMultiHandle'], 'curl_multi_exec' => ['int', 'multi_handle'=>'CurlMultiHandle', '&w_still_running'=>'int'], -'curl_multi_getcontent' => ['string', 'handle'=>'CurlHandle'], +'curl_multi_getcontent' => ['?string', 'handle'=>'CurlHandle'], 'curl_multi_info_read' => ['array|false', 'multi_handle'=>'CurlMultiHandle', '&w_queued_messages='=>'int'], 'curl_multi_init' => ['CurlMultiHandle|false'], 'curl_multi_remove_handle' => ['int', 'multi_handle'=>'CurlMultiHandle', 'handle'=>'CurlHandle'], @@ -1744,7 +1744,7 @@ 'datefmt_format' => ['string|false', 'formatter'=>'IntlDateFormatter', 'datetime'=>'DateTime|IntlCalendar|array|int'], 'datefmt_format_object' => ['string|false', 'datetime'=>'object', 'format='=>'mixed', 'locale='=>'string'], 'datefmt_get_calendar' => ['int', 'formatter'=>'IntlDateFormatter'], -'datefmt_get_calendar_object' => ['IntlCalendar', 'formatter'=>'IntlDateFormatter'], +'datefmt_get_calendar_object' => ['IntlCalendar|false|null', 'formatter'=>'IntlDateFormatter'], 'datefmt_get_datetype' => ['int', 'formatter'=>'IntlDateFormatter'], 'datefmt_get_error_code' => ['int', 'formatter'=>'IntlDateFormatter'], 'datefmt_get_error_message' => ['string', 'formatter'=>'IntlDateFormatter'], @@ -3362,7 +3362,7 @@ 'ftp_put' => ['bool', 'ftp'=>'FTP\Connection', 'remote_filename'=>'string', 'local_filename'=>'string', 'mode='=>'int', 'offset='=>'int'], 'ftp_pwd' => ['string|false', 'ftp'=>'FTP\Connection'], 'ftp_quit' => ['bool', 'ftp'=>'FTP\Connection'], -'ftp_raw' => ['array', 'ftp'=>'FTP\Connection', 'command'=>'string'], +'ftp_raw' => ['?array', 'ftp'=>'FTP\Connection', 'command'=>'string'], 'ftp_rawlist' => ['array|false', 'ftp'=>'FTP\Connection', 'directory'=>'string', 'recursive='=>'bool'], 'ftp_rename' => ['bool', 'ftp'=>'FTP\Connection', 'from'=>'string', 'to'=>'string'], 'ftp_rmdir' => ['bool', 'ftp'=>'FTP\Connection', 'directory'=>'string'], @@ -6190,7 +6190,7 @@ 'intlcal_after' => ['bool', 'calendar'=>'IntlCalendar', 'other'=>'IntlCalendar'], 'intlcal_before' => ['bool', 'calendar'=>'IntlCalendar', 'other'=>'IntlCalendar'], 'intlcal_clear' => ['bool', 'calendar'=>'IntlCalendar', 'field='=>'int'], -'intlcal_create_instance' => ['IntlCalendar', 'timezone='=>'mixed', 'locale='=>'string'], +'intlcal_create_instance' => ['?IntlCalendar', 'timezone='=>'mixed', 'locale='=>'string'], 'intlcal_equals' => ['bool', 'calendar'=>'IntlCalendar', 'other'=>'IntlCalendar'], 'intlcal_field_difference' => ['int', 'calendar'=>'IntlCalendar', 'timestamp'=>'float', 'field'=>'int'], 'intlcal_from_date_time' => ['IntlCalendar', 'datetime'=>'DateTime|string'], @@ -6503,8 +6503,8 @@ 'IntlTimeZone::useDaylightTime' => ['bool'], 'intltz_count_equivalent_ids' => ['int', 'timezoneId'=>'string'], 'intltz_create_enumeration' => ['IntlIterator', 'countryOrRawOffset'=>'mixed'], -'intltz_create_time_zone' => ['IntlTimeZone', 'timezoneId'=>'string'], -'intltz_from_date_time_zone' => ['IntlTimeZone', 'timezone'=>'DateTimeZone'], +'intltz_create_time_zone' => ['?IntlTimeZone', 'timezoneId'=>'string'], +'intltz_from_date_time_zone' => ['?IntlTimeZone', 'timezone'=>'DateTimeZone'], 'intltz_get_canonical_id' => ['string', 'timezoneId'=>'string', '&isSystemId'=>'bool'], 'intltz_get_display_name' => ['string', 'timezone'=>'IntlTimeZone', 'dst'=>'bool', 'style'=>'int', 'locale'=>'string'], 'intltz_get_dst_savings' => ['int', 'timezone'=>'IntlTimeZone'], @@ -6923,22 +6923,22 @@ 'Locale::parseLocale' => ['array', 'locale'=>'string'], 'Locale::setDefault' => ['bool', 'locale'=>'string'], 'locale_accept_from_http' => ['string|false', 'header'=>'string'], -'locale_canonicalize' => ['string', 'locale'=>'string'], +'locale_canonicalize' => ['?string', 'locale'=>'string'], 'locale_compose' => ['string|false', 'subtags'=>'array'], -'locale_filter_matches' => ['bool', 'languageTag'=>'string', 'locale'=>'string', 'canonicalize='=>'bool'], -'locale_get_all_variants' => ['array', 'locale'=>'string'], +'locale_filter_matches' => ['?bool', 'languageTag'=>'string', 'locale'=>'string', 'canonicalize='=>'bool'], +'locale_get_all_variants' => ['?array', 'locale'=>'string'], 'locale_get_default' => ['string'], 'locale_get_display_language' => ['string', 'locale'=>'string', 'displayLocale='=>'string'], 'locale_get_display_name' => ['string', 'locale'=>'string', 'displayLocale='=>'string'], 'locale_get_display_region' => ['string', 'locale'=>'string', 'displayLocale='=>'string'], 'locale_get_display_script' => ['string', 'locale'=>'string', 'displayLocale='=>'string'], 'locale_get_display_variant' => ['string', 'locale'=>'string', 'displayLocale='=>'string'], -'locale_get_keywords' => ['array|false', 'locale'=>'string'], -'locale_get_primary_language' => ['string', 'locale'=>'string'], -'locale_get_region' => ['string', 'locale'=>'string'], -'locale_get_script' => ['string', 'locale'=>'string'], -'locale_lookup' => ['string', 'languageTag'=>'array', 'locale'=>'string', 'canonicalize='=>'bool', 'defaultLocale='=>'string'], -'locale_parse' => ['array', 'locale'=>'string'], +'locale_get_keywords' => ['array|false|null', 'locale'=>'string'], +'locale_get_primary_language' => ['?string', 'locale'=>'string'], +'locale_get_region' => ['?string', 'locale'=>'string'], +'locale_get_script' => ['?string', 'locale'=>'string'], +'locale_lookup' => ['?string', 'languageTag'=>'array', 'locale'=>'string', 'canonicalize='=>'bool', 'defaultLocale='=>'string'], +'locale_parse' => ['?array', 'locale'=>'string'], 'locale_set_default' => ['bool', 'locale'=>'string'], 'localeconv' => ['array'], 'localtime' => ['array', 'timestamp='=>'int', 'associative='=>'bool'], @@ -7279,7 +7279,7 @@ 'mb_ereg' => ['bool', 'pattern'=>'string', 'string'=>'string', '&w_matches='=>'array|null'], 'mb_ereg_match' => ['bool', 'pattern'=>'string', 'string'=>'string', 'options='=>'string|null'], 'mb_ereg_replace' => ['string|false|null', 'pattern'=>'string', 'replacement'=>'string', 'string'=>'string', 'options='=>'string|null'], -'mb_ereg_replace_callback' => ['string|false', 'pattern'=>'string', 'callback'=>'callable', 'string'=>'string', 'options='=>'string|null'], +'mb_ereg_replace_callback' => ['string|false|null', 'pattern'=>'string', 'callback'=>'callable', 'string'=>'string', 'options='=>'string|null'], 'mb_ereg_search' => ['bool', 'pattern='=>'string|null', 'options='=>'string|null'], 'mb_ereg_search_getpos' => ['int'], 'mb_ereg_search_getregs' => ['string[]|false'], @@ -8167,7 +8167,7 @@ 'msg_send' => ['bool', 'queue'=>'resource', 'message_type'=>'int', 'message'=>'mixed', 'serialize='=>'bool', 'blocking='=>'bool', '&w_error_code='=>'int'], 'msg_set_queue' => ['bool', 'queue'=>'resource', 'data'=>'array'], 'msg_stat_queue' => ['array', 'queue'=>'resource'], -'msgfmt_create' => ['MessageFormatter', 'locale'=>'string', 'pattern'=>'string'], +'msgfmt_create' => ['?MessageFormatter', 'locale'=>'string', 'pattern'=>'string'], 'msgfmt_format' => ['string|false', 'formatter'=>'MessageFormatter', 'values'=>'array'], 'msgfmt_format_message' => ['string|false', 'locale'=>'string', 'pattern'=>'string', 'values'=>'array'], 'msgfmt_get_error_code' => ['int', 'formatter'=>'MessageFormatter'], @@ -8481,7 +8481,7 @@ 'mysqli_field_tell' => ['int', 'result'=>'mysqli_result'], 'mysqli_free_result' => ['void', 'result'=>'mysqli_result'], 'mysqli_get_cache_stats' => ['array|false'], -'mysqli_get_charset' => ['object', 'mysql'=>'mysqli'], +'mysqli_get_charset' => ['?object', 'mysql'=>'mysqli'], 'mysqli_get_client_info' => ['string', 'mysql='=>'?mysqli'], 'mysqli_get_client_stats' => ['array'], 'mysqli_get_client_version' => ['int', 'link'=>'mysqli'], @@ -11783,7 +11783,7 @@ 'SAMConnection::unsubscribe' => ['bool', 'subscriptionid'=>'string', 'targettopic='=>'string'], 'SAMMessage::body' => ['string'], 'SAMMessage::header' => ['object'], -'sapi_windows_cp_conv' => ['string', 'in_codepage'=>'int|string', 'out_codepage'=>'int|string', 'subject'=>'string'], +'sapi_windows_cp_conv' => ['?string', 'in_codepage'=>'int|string', 'out_codepage'=>'int|string', 'subject'=>'string'], 'sapi_windows_cp_get' => ['int'], 'sapi_windows_cp_is_utf8' => ['bool'], 'sapi_windows_cp_set' => ['bool', 'codepage'=>'int'], @@ -13786,7 +13786,7 @@ 'strcoll' => ['int', 'string1'=>'string', 'string2'=>'string'], 'strcspn' => ['int', 'string'=>'string', 'characters'=>'string', 'offset='=>'int', 'length='=>'int'], 'stream_bucket_append' => ['void', 'brigade'=>'resource', 'bucket'=>'object'], -'stream_bucket_make_writeable' => ['object', 'brigade'=>'resource'], +'stream_bucket_make_writeable' => ['?object', 'brigade'=>'resource'], 'stream_bucket_new' => ['object|false', 'stream'=>'resource', 'buffer'=>'string'], 'stream_bucket_prepend' => ['void', 'brigade'=>'resource', 'bucket'=>'object'], 'stream_context_create' => ['resource', 'options='=>'array', 'params='=>'array'], @@ -14590,16 +14590,16 @@ 'tidy_config_count' => ['int', 'tidy'=>'tidy'], 'tidy_diagnose' => ['bool', 'tidy'=>'tidy'], 'tidy_error_count' => ['int', 'tidy'=>'tidy'], -'tidy_get_body' => ['tidyNode', 'tidy'=>'tidy'], +'tidy_get_body' => ['?tidyNode', 'tidy'=>'tidy'], 'tidy_get_config' => ['array', 'tidy'=>'tidy'], 'tidy_get_error_buffer' => ['string', 'tidy'=>'tidy'], -'tidy_get_head' => ['tidyNode', 'tidy'=>'tidy'], -'tidy_get_html' => ['tidyNode', 'tidy'=>'tidy'], +'tidy_get_head' => ['?tidyNode', 'tidy'=>'tidy'], +'tidy_get_html' => ['?tidyNode', 'tidy'=>'tidy'], 'tidy_get_html_ver' => ['int', 'tidy'=>'tidy'], 'tidy_get_opt_doc' => ['string', 'tidy'=>'tidy', 'option'=>'string'], 'tidy_get_output' => ['string', 'tidy'=>'tidy'], 'tidy_get_release' => ['string'], -'tidy_get_root' => ['tidyNode', 'tidy'=>'tidy'], +'tidy_get_root' => ['?tidyNode', 'tidy'=>'tidy'], 'tidy_get_status' => ['int', 'tidy'=>'tidy'], 'tidy_getopt' => ['mixed', 'tidy'=>'string', 'option'=>'tidy'], 'tidy_is_xhtml' => ['bool', 'tidy'=>'tidy'], @@ -14868,7 +14868,7 @@ 'Transliterator::transliterate' => ['string|false', 'subject'=>'string', 'start='=>'int', 'end='=>'int'], 'transliterator_create' => ['?Transliterator', 'id'=>'string', 'direction='=>'int'], 'transliterator_create_from_rules' => ['?Transliterator', 'rules'=>'string', 'direction='=>'int'], -'transliterator_create_inverse' => ['Transliterator', 'transliterator'=>'Transliterator'], +'transliterator_create_inverse' => ['?Transliterator', 'transliterator'=>'Transliterator'], 'transliterator_get_error_code' => ['int', 'transliterator'=>'Transliterator'], 'transliterator_get_error_message' => ['string', 'transliterator'=>'Transliterator'], 'transliterator_list_ids' => ['array'], @@ -15597,7 +15597,7 @@ 'xhprof_sample_enable' => ['void'], 'xlswriter_get_author' => ['string'], 'xlswriter_get_version' => ['string'], -'xml_error_string' => ['string', 'error_code'=>'int'], +'xml_error_string' => ['?string', 'error_code'=>'int'], 'xml_get_current_byte_index' => ['int|false', 'parser'=>'XMLParser'], 'xml_get_current_column_number' => ['int|false', 'parser'=>'XMLParser'], 'xml_get_current_line_number' => ['int|false', 'parser'=>'XMLParser'], diff --git a/dictionaries/CallMap_71_delta.php b/dictionaries/CallMap_71_delta.php index 987bf2b861a..89451449402 100644 --- a/dictionaries/CallMap_71_delta.php +++ b/dictionaries/CallMap_71_delta.php @@ -26,7 +26,7 @@ 'openssl_get_curve_names' => ['list'], 'pcntl_async_signals' => ['bool', 'enable='=>'bool'], 'pcntl_signal_get_handler' => ['int|string', 'signal'=>'int'], - 'sapi_windows_cp_conv' => ['string', 'in_codepage'=>'int|string', 'out_codepage'=>'int|string', 'subject'=>'string'], + 'sapi_windows_cp_conv' => ['?string', 'in_codepage'=>'int|string', 'out_codepage'=>'int|string', 'subject'=>'string'], 'sapi_windows_cp_get' => ['int'], 'sapi_windows_cp_is_utf8' => ['bool'], 'sapi_windows_cp_set' => ['bool', 'codepage'=>'int'], diff --git a/dictionaries/CallMap_80_delta.php b/dictionaries/CallMap_80_delta.php index e7e4323b5a2..d03035cb424 100644 --- a/dictionaries/CallMap_80_delta.php +++ b/dictionaries/CallMap_80_delta.php @@ -278,8 +278,8 @@ 'new' => ['int', 'multi_handle'=>'CurlMultiHandle', '&w_still_running'=>'int'], ], 'curl_multi_getcontent' => [ - 'old' => ['string', 'ch'=>'resource'], - 'new' => ['string', 'handle'=>'CurlHandle'], + 'old' => ['?string', 'ch'=>'resource'], + 'new' => ['?string', 'handle'=>'CurlHandle'], ], 'curl_multi_info_read' => [ 'old' => ['array|false', 'mh'=>'resource', '&w_msgs_in_queue='=>'int'], @@ -834,8 +834,8 @@ 'new' => ['string|false|null', 'pattern'=>'string', 'replacement'=>'string', 'string'=>'string', 'options='=>'string|null'], ], 'mb_ereg_replace_callback' => [ - 'old' => ['string|false', 'pattern'=>'string', 'callback'=>'callable', 'string'=>'string', 'options='=>'string'], - 'new' => ['string|false', 'pattern'=>'string', 'callback'=>'callable', 'string'=>'string', 'options='=>'string|null'], + 'old' => ['string|false|null', 'pattern'=>'string', 'callback'=>'callable', 'string'=>'string', 'options='=>'string'], + 'new' => ['string|false|null', 'pattern'=>'string', 'callback'=>'callable', 'string'=>'string', 'options='=>'string|null'], ], 'mb_ereg_search' => [ 'old' => ['bool', 'pattern='=>'string', 'options='=>'string'], diff --git a/dictionaries/CallMap_81_delta.php b/dictionaries/CallMap_81_delta.php index f6299baad94..5651886c22a 100644 --- a/dictionaries/CallMap_81_delta.php +++ b/dictionaries/CallMap_81_delta.php @@ -106,8 +106,8 @@ 'new' => ['bool', 'ftp' => 'FTP\Connection', 'command' => 'string'], ], 'ftp_raw' => [ - 'old' => ['array', 'ftp' => 'resource', 'command' => 'string'], - 'new' => ['array', 'ftp' => 'FTP\Connection', 'command' => 'string'], + 'old' => ['?array', 'ftp' => 'resource', 'command' => 'string'], + 'new' => ['?array', 'ftp' => 'FTP\Connection', 'command' => 'string'], ], 'ftp_mkdir' => [ 'old' => ['string|false', 'ftp' => 'resource', 'directory' => 'string'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 0f5a0f2d4c0..bb7bec1c0c5 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -9909,7 +9909,7 @@ 'classkit_method_remove' => ['bool', 'classname'=>'string', 'methodname'=>'string'], 'classkit_method_rename' => ['bool', 'classname'=>'string', 'methodname'=>'string', 'newname'=>'string'], 'clearstatcache' => ['void', 'clear_realpath_cache='=>'bool', 'filename='=>'string'], - 'cli_get_process_title' => ['string'], + 'cli_get_process_title' => ['?string'], 'cli_set_process_title' => ['bool', 'title'=>'string'], 'closedir' => ['void', 'dir_handle='=>'resource'], 'closelog' => ['bool'], @@ -9920,7 +9920,7 @@ 'clusterObj::setGroup' => ['int', 'expression'=>'string'], 'collator_asort' => ['bool', 'object'=>'collator', '&rw_array'=>'array', 'flags='=>'int'], 'collator_compare' => ['int', 'object'=>'collator', 'string1'=>'string', 'string2'=>'string'], - 'collator_create' => ['Collator', 'locale'=>'string'], + 'collator_create' => ['?Collator', 'locale'=>'string'], 'collator_get_attribute' => ['int|false', 'object'=>'collator', 'attribute'=>'int'], 'collator_get_error_code' => ['int', 'object'=>'collator'], 'collator_get_error_message' => ['string', 'object'=>'collator'], @@ -10135,7 +10135,7 @@ 'curl_multi_add_handle' => ['int', 'mh'=>'resource', 'ch'=>'resource'], 'curl_multi_close' => ['void', 'mh'=>'resource'], 'curl_multi_exec' => ['int', 'mh'=>'resource', '&w_still_running'=>'int'], - 'curl_multi_getcontent' => ['string', 'ch'=>'resource'], + 'curl_multi_getcontent' => ['?string', 'ch'=>'resource'], 'curl_multi_info_read' => ['array|false', 'mh'=>'resource', '&w_msgs_in_queue='=>'int'], 'curl_multi_init' => ['resource|false'], 'curl_multi_remove_handle' => ['int', 'mh'=>'resource', 'ch'=>'resource'], @@ -10191,7 +10191,7 @@ 'datefmt_format' => ['string|false', 'formatter'=>'IntlDateFormatter', 'datetime'=>'DateTime|IntlCalendar|array|int'], 'datefmt_format_object' => ['string|false', 'datetime'=>'object', 'format='=>'mixed', 'locale='=>'string'], 'datefmt_get_calendar' => ['int', 'formatter'=>'IntlDateFormatter'], - 'datefmt_get_calendar_object' => ['IntlCalendar', 'formatter'=>'IntlDateFormatter'], + 'datefmt_get_calendar_object' => ['IntlCalendar|false|null', 'formatter'=>'IntlDateFormatter'], 'datefmt_get_datetype' => ['int', 'formatter'=>'IntlDateFormatter'], 'datefmt_get_error_code' => ['int', 'formatter'=>'IntlDateFormatter'], 'datefmt_get_error_message' => ['string', 'formatter'=>'IntlDateFormatter'], @@ -10923,7 +10923,7 @@ 'ftp_put' => ['bool', 'ftp'=>'resource', 'remote_filename'=>'string', 'local_filename'=>'string', 'mode='=>'int', 'offset='=>'int'], 'ftp_pwd' => ['string|false', 'ftp'=>'resource'], 'ftp_quit' => ['bool', 'ftp'=>'resource'], - 'ftp_raw' => ['array', 'ftp'=>'resource', 'command'=>'string'], + 'ftp_raw' => ['?array', 'ftp'=>'resource', 'command'=>'string'], 'ftp_rawlist' => ['array|false', 'ftp'=>'resource', 'directory'=>'string', 'recursive='=>'bool'], 'ftp_rename' => ['bool', 'ftp'=>'resource', 'from'=>'string', 'to'=>'string'], 'ftp_rmdir' => ['bool', 'ftp'=>'resource', 'directory'=>'string'], @@ -12272,7 +12272,7 @@ 'intlcal_after' => ['bool', 'calendar'=>'IntlCalendar', 'other'=>'IntlCalendar'], 'intlcal_before' => ['bool', 'calendar'=>'IntlCalendar', 'other'=>'IntlCalendar'], 'intlcal_clear' => ['bool', 'calendar'=>'IntlCalendar', 'field='=>'int'], - 'intlcal_create_instance' => ['IntlCalendar', 'timezone='=>'mixed', 'locale='=>'string'], + 'intlcal_create_instance' => ['?IntlCalendar', 'timezone='=>'mixed', 'locale='=>'string'], 'intlcal_equals' => ['bool', 'calendar'=>'IntlCalendar', 'other'=>'IntlCalendar'], 'intlcal_field_difference' => ['int', 'calendar'=>'IntlCalendar', 'timestamp'=>'float', 'field'=>'int'], 'intlcal_from_date_time' => ['IntlCalendar', 'datetime'=>'DateTime|string'], @@ -12317,8 +12317,8 @@ 'intlgregcal_set_gregorian_change' => ['void', 'calendar'=>'IntlGregorianCalendar', 'timestamp'=>'float'], 'intltz_count_equivalent_ids' => ['int', 'timezoneId'=>'string'], 'intltz_create_enumeration' => ['IntlIterator', 'countryOrRawOffset'=>'mixed'], - 'intltz_create_time_zone' => ['IntlTimeZone', 'timezoneId'=>'string'], - 'intltz_from_date_time_zone' => ['IntlTimeZone', 'timezone'=>'DateTimeZone'], + 'intltz_create_time_zone' => ['?IntlTimeZone', 'timezoneId'=>'string'], + 'intltz_from_date_time_zone' => ['?IntlTimeZone', 'timezone'=>'DateTimeZone'], 'intltz_getGMT' => ['IntlTimeZone'], 'intltz_get_canonical_id' => ['string', 'timezoneId'=>'string', '&isSystemId'=>'bool'], 'intltz_get_display_name' => ['string', 'timezone'=>'IntlTimeZone', 'dst'=>'bool', 'style'=>'int', 'locale'=>'string'], @@ -12558,22 +12558,22 @@ 'litespeed_request_headers' => ['array'], 'litespeed_response_headers' => ['array'], 'locale_accept_from_http' => ['string|false', 'header'=>'string'], - 'locale_canonicalize' => ['string', 'locale'=>'string'], + 'locale_canonicalize' => ['?string', 'locale'=>'string'], 'locale_compose' => ['string|false', 'subtags'=>'array'], - 'locale_filter_matches' => ['bool', 'languageTag'=>'string', 'locale'=>'string', 'canonicalize='=>'bool'], - 'locale_get_all_variants' => ['array', 'locale'=>'string'], + 'locale_filter_matches' => ['?bool', 'languageTag'=>'string', 'locale'=>'string', 'canonicalize='=>'bool'], + 'locale_get_all_variants' => ['?array', 'locale'=>'string'], 'locale_get_default' => ['string'], 'locale_get_display_language' => ['string', 'locale'=>'string', 'displayLocale='=>'string'], 'locale_get_display_name' => ['string', 'locale'=>'string', 'displayLocale='=>'string'], 'locale_get_display_region' => ['string', 'locale'=>'string', 'displayLocale='=>'string'], 'locale_get_display_script' => ['string', 'locale'=>'string', 'displayLocale='=>'string'], 'locale_get_display_variant' => ['string', 'locale'=>'string', 'displayLocale='=>'string'], - 'locale_get_keywords' => ['array|false', 'locale'=>'string'], - 'locale_get_primary_language' => ['string', 'locale'=>'string'], - 'locale_get_region' => ['string', 'locale'=>'string'], - 'locale_get_script' => ['string', 'locale'=>'string'], - 'locale_lookup' => ['string', 'languageTag'=>'array', 'locale'=>'string', 'canonicalize='=>'bool', 'defaultLocale='=>'string'], - 'locale_parse' => ['array', 'locale'=>'string'], + 'locale_get_keywords' => ['array|false|null', 'locale'=>'string'], + 'locale_get_primary_language' => ['?string', 'locale'=>'string'], + 'locale_get_region' => ['?string', 'locale'=>'string'], + 'locale_get_script' => ['?string', 'locale'=>'string'], + 'locale_lookup' => ['?string', 'languageTag'=>'array', 'locale'=>'string', 'canonicalize='=>'bool', 'defaultLocale='=>'string'], + 'locale_parse' => ['?array', 'locale'=>'string'], 'locale_set_default' => ['bool', 'locale'=>'string'], 'localeconv' => ['array'], 'localtime' => ['array', 'timestamp='=>'int', 'associative='=>'bool'], @@ -12893,7 +12893,7 @@ 'mb_ereg' => ['int|false', 'pattern'=>'string', 'string'=>'string', '&w_matches='=>'array|null'], 'mb_ereg_match' => ['bool', 'pattern'=>'string', 'string'=>'string', 'options='=>'string'], 'mb_ereg_replace' => ['string|false', 'pattern'=>'string', 'replacement'=>'string', 'string'=>'string', 'options='=>'string'], - 'mb_ereg_replace_callback' => ['string|false', 'pattern'=>'string', 'callback'=>'callable', 'string'=>'string', 'options='=>'string'], + 'mb_ereg_replace_callback' => ['string|false|null', 'pattern'=>'string', 'callback'=>'callable', 'string'=>'string', 'options='=>'string'], 'mb_ereg_search' => ['bool', 'pattern='=>'string', 'options='=>'string'], 'mb_ereg_search_getpos' => ['int'], 'mb_ereg_search_getregs' => ['string[]|false'], @@ -13122,7 +13122,7 @@ 'msg_send' => ['bool', 'queue'=>'resource', 'message_type'=>'int', 'message'=>'mixed', 'serialize='=>'bool', 'blocking='=>'bool', '&w_error_code='=>'int'], 'msg_set_queue' => ['bool', 'queue'=>'resource', 'data'=>'array'], 'msg_stat_queue' => ['array', 'queue'=>'resource'], - 'msgfmt_create' => ['MessageFormatter', 'locale'=>'string', 'pattern'=>'string'], + 'msgfmt_create' => ['?MessageFormatter', 'locale'=>'string', 'pattern'=>'string'], 'msgfmt_format' => ['string|false', 'formatter'=>'MessageFormatter', 'values'=>'array'], 'msgfmt_format_message' => ['string|false', 'locale'=>'string', 'pattern'=>'string', 'values'=>'array'], 'msgfmt_get_error_code' => ['int', 'formatter'=>'MessageFormatter'], @@ -13418,7 +13418,7 @@ 'mysqli_field_tell' => ['int', 'result'=>'mysqli_result'], 'mysqli_free_result' => ['void', 'result'=>'mysqli_result'], 'mysqli_get_cache_stats' => ['array|false'], - 'mysqli_get_charset' => ['object', 'mysql'=>'mysqli'], + 'mysqli_get_charset' => ['?object', 'mysql'=>'mysqli'], 'mysqli_get_client_info' => ['string', 'mysql='=>'?mysqli'], 'mysqli_get_client_stats' => ['array'], 'mysqli_get_client_version' => ['int', 'link'=>'mysqli'], @@ -15221,7 +15221,7 @@ 'streamWrapper::unlink' => ['bool', 'path'=>'string'], 'streamWrapper::url_stat' => ['array', 'path'=>'string', 'flags'=>'int'], 'stream_bucket_append' => ['void', 'brigade'=>'resource', 'bucket'=>'object'], - 'stream_bucket_make_writeable' => ['object', 'brigade'=>'resource'], + 'stream_bucket_make_writeable' => ['?object', 'brigade'=>'resource'], 'stream_bucket_new' => ['object|false', 'stream'=>'resource', 'buffer'=>'string'], 'stream_bucket_prepend' => ['void', 'brigade'=>'resource', 'bucket'=>'object'], 'stream_context_create' => ['resource', 'options='=>'array', 'params='=>'array'], @@ -15739,16 +15739,16 @@ 'tidy_config_count' => ['int', 'tidy'=>'tidy'], 'tidy_diagnose' => ['bool', 'tidy'=>'tidy'], 'tidy_error_count' => ['int', 'tidy'=>'tidy'], - 'tidy_get_body' => ['tidyNode', 'tidy'=>'tidy'], + 'tidy_get_body' => ['?tidyNode', 'tidy'=>'tidy'], 'tidy_get_config' => ['array', 'tidy'=>'tidy'], 'tidy_get_error_buffer' => ['string', 'tidy'=>'tidy'], - 'tidy_get_head' => ['tidyNode', 'tidy'=>'tidy'], - 'tidy_get_html' => ['tidyNode', 'tidy'=>'tidy'], + 'tidy_get_head' => ['?tidyNode', 'tidy'=>'tidy'], + 'tidy_get_html' => ['?tidyNode', 'tidy'=>'tidy'], 'tidy_get_html_ver' => ['int', 'tidy'=>'tidy'], 'tidy_get_opt_doc' => ['string', 'tidy'=>'tidy', 'option'=>'string'], 'tidy_get_output' => ['string', 'tidy'=>'tidy'], 'tidy_get_release' => ['string'], - 'tidy_get_root' => ['tidyNode', 'tidy'=>'tidy'], + 'tidy_get_root' => ['?tidyNode', 'tidy'=>'tidy'], 'tidy_get_status' => ['int', 'tidy'=>'tidy'], 'tidy_getopt' => ['mixed', 'tidy'=>'string', 'option'=>'tidy'], 'tidy_is_xhtml' => ['bool', 'tidy'=>'tidy'], @@ -15945,7 +15945,7 @@ 'trait_exists' => ['bool', 'trait'=>'string', 'autoload='=>'bool'], 'transliterator_create' => ['?Transliterator', 'id'=>'string', 'direction='=>'int'], 'transliterator_create_from_rules' => ['?Transliterator', 'rules'=>'string', 'direction='=>'int'], - 'transliterator_create_inverse' => ['Transliterator', 'transliterator'=>'Transliterator'], + 'transliterator_create_inverse' => ['?Transliterator', 'transliterator'=>'Transliterator'], 'transliterator_get_error_code' => ['int', 'transliterator'=>'Transliterator'], 'transliterator_get_error_message' => ['string', 'transliterator'=>'Transliterator'], 'transliterator_list_ids' => ['array'], @@ -16412,7 +16412,7 @@ 'xhprof_sample_enable' => ['void'], 'xlswriter_get_author' => ['string'], 'xlswriter_get_version' => ['string'], - 'xml_error_string' => ['string', 'error_code'=>'int'], + 'xml_error_string' => ['?string', 'error_code'=>'int'], 'xml_get_current_byte_index' => ['int|false', 'parser'=>'resource'], 'xml_get_current_column_number' => ['int|false', 'parser'=>'resource'], 'xml_get_current_line_number' => ['int|false', 'parser'=>'resource'], diff --git a/stubs/CoreGenericFunctions.phpstub b/stubs/CoreGenericFunctions.phpstub index 4d33ebbd053..54bd9d8f27b 100644 --- a/stubs/CoreGenericFunctions.phpstub +++ b/stubs/CoreGenericFunctions.phpstub @@ -1275,7 +1275,7 @@ function pack(string $format, mixed ...$values) {} * @param string|int $out_codepage * @psalm-flow ($subject) -> return */ -function sapi_windows_cp_conv($in_codepage, $out_codepage, string $subject) : string {} +function sapi_windows_cp_conv($in_codepage, $out_codepage, string $subject) : ?string {} /** * @psalm-pure diff --git a/tests/Internal/Codebase/InternalCallMapHandlerTest.php b/tests/Internal/Codebase/InternalCallMapHandlerTest.php index e69385648bc..164fb4143d1 100644 --- a/tests/Internal/Codebase/InternalCallMapHandlerTest.php +++ b/tests/Internal/Codebase/InternalCallMapHandlerTest.php @@ -750,7 +750,7 @@ private function assertParameter(array $normalizedEntry, ReflectionParameter $pa $expectedType = $param->getType(); if (isset($expectedType) && !empty($normalizedEntry['type'])) { - $this->assertTypeValidity($expectedType, $normalizedEntry['type'], "Param '{$name}' has incorrect type"); + $this->assertTypeValidity($expectedType, $normalizedEntry['type'], false, "Param '{$name}' has incorrect type"); } } @@ -771,20 +771,19 @@ public function assertEntryReturnType(ReflectionFunction $function, string $entr return; } - $this->assertTypeValidity($expectedType, $entryReturnType, 'CallMap entry has incorrect return type'); + $this->assertTypeValidity($expectedType, $entryReturnType, true, 'CallMap entry has incorrect return type'); } /** * Since string equality is too strict, we do some extra checking here */ - private function assertTypeValidity(ReflectionType $reflected, string $specified, string $message): void + private function assertTypeValidity(ReflectionType $reflected, string $specified, bool $checkNullable, string $message): void { $expectedType = Reflection::getPsalmTypeFromReflectionType($reflected); - - $parsedType = Type::parseString($specified); + $callMapType = Type::parseString($specified); try { - $this->assertTrue(UnionTypeComparator::isContainedBy(self::$codebase, $parsedType, $expectedType), $message); + $this->assertTrue(UnionTypeComparator::isContainedBy(self::$codebase, $callMapType, $expectedType), $message); } catch (InvalidArgumentException $e) { if (preg_match('/^Could not get class storage for (.*)$/', $e->getMessage(), $matches) && !class_exists($matches[1]) @@ -792,5 +791,10 @@ private function assertTypeValidity(ReflectionType $reflected, string $specified $this->fail("Class used in CallMap does not exist: {$matches[1]}"); } } + + // Reflection::getPsalmTypeFromReflectionType adds |null to mixed types so skip comparison + if ($checkNullable && !$expectedType->hasMixed()) { + $this->assertSame($expectedType->isNullable(), $callMapType->isNullable(), $message); + } } } From 1e0b57226494f7aa4608bf9e7e681c5976e85fba Mon Sep 17 00:00:00 2001 From: someniatko Date: Thu, 14 Jul 2022 10:03:47 +0300 Subject: [PATCH 017/178] #8200 - bikeshedding the tests --- tests/Template/ClassTemplateTest.php | 154 +++++++++++++-------------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/tests/Template/ClassTemplateTest.php b/tests/Template/ClassTemplateTest.php index 73a0fa9d811..413f13add1f 100644 --- a/tests/Template/ClassTemplateTest.php +++ b/tests/Template/ClassTemplateTest.php @@ -3697,107 +3697,107 @@ protected function setUp(): void 'return TemplatedClass' => [ ' - * - * @psalm-pure + * @template-covariant A + * @psalm-immutable */ - public static function just($value): self + final class Maybe { - return new self($value); - } - } + /** + * @param null|A $value + */ + public function __construct(private $value = null) {} - abstract class Test - { - final private function __construct() {} + /** + * @template B + * @param B $value + * @return Maybe + * + * @psalm-pure + */ + public static function just($value): self + { + return new self($value); + } + } - /** @return Maybe */ - final public static function create(): Maybe + abstract class Test { - return Maybe::just(new static()); - } - }', + final private function __construct() {} + + /** @return Maybe */ + final public static function create(): Maybe + { + return Maybe::just(new static()); + } + }', ], 'return list created in a static method of another class' => [ ' - * - * @psalm-pure - */ - public static function mklist($value): array + final class Lister { - return [ $value ]; + /** + * @template B + * @param B $value + * @return list + * + * @psalm-pure + */ + public static function mklist($value): array + { + return [ $value ]; + } } - } - - abstract class Test - { - final private function __construct() {} - /** @return list */ - final public static function create(): array + abstract class Test { - return Lister::mklist(new static()); - } - }', + final private function __construct() {} + + /** @return list */ + final public static function create(): array + { + return Lister::mklist(new static()); + } + }', ], 'use TemplatedClass as an intermediate variable inside a method' => [ ' - * - * @psalm-pure + * @template-covariant A + * @psalm-immutable */ - public static function just($value): self + final class Maybe { - return new self($value); - } - } + /** + * @param A $value + */ + public function __construct(public $value) {} - abstract class Test - { - final private function __construct() {} + /** + * @template B + * @param B $value + * @return Maybe + * + * @psalm-pure + */ + public static function just($value): self + { + return new self($value); + } + } - final public static function create(): static + abstract class Test { - $maybe = Maybe::just(new static()); - return $maybe->value; - } - }', + final private function __construct() {} + + final public static function create(): static + { + $maybe = Maybe::just(new static()); + return $maybe->value; + } + }', ], ]; } From bb760a22241d31ad0ccee109fc7b6484ca067e7a Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Thu, 21 Jul 2022 11:32:24 +0200 Subject: [PATCH 018/178] fix race conditions causing notices if directory does not exist --- src/Psalm/Config.php | 28 ++++++++++++++----- .../Internal/Provider/ParserCacheProvider.php | 8 ++++++ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index 08bc9604f2e..28e4ecbc95c 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -52,6 +52,7 @@ use function basename; use function chdir; use function class_exists; +use function clearstatcache; use function count; use function dirname; use function error_log; @@ -2268,6 +2269,7 @@ public function getPotentialComposerFilePathForClassLike(string $class): ?string public static function removeCacheDirectory(string $dir): void { + clearstatcache(true, $dir); if (is_dir($dir)) { $objects = scandir($dir, SCANDIR_SORT_NONE); @@ -2276,17 +2278,29 @@ public static function removeCacheDirectory(string $dir): void } foreach ($objects as $object) { - if ($object !== '.' && $object !== '..') { - if (filetype($dir . '/' . $object) === 'dir') { - self::removeCacheDirectory($dir . '/' . $object); - } else { - unlink($dir . '/' . $object); - } + if ($object === '.' || $object === '..') { + continue; + } + + // if it was deleted in the meantime/race condition with other psalm process + if (!file_exists($dir . '/' . $object)) { + continue; + } + + if (filetype($dir . '/' . $object) === 'dir') { + self::removeCacheDirectory($dir . '/' . $object); + } else { + unlink($dir . '/' . $object); } } reset($objects); - rmdir($dir); + + // may have been removed in the meantime + clearstatcache(true, $dir); + if (is_dir($dir)) { + rmdir($dir); + } } } diff --git a/src/Psalm/Internal/Provider/ParserCacheProvider.php b/src/Psalm/Internal/Provider/ParserCacheProvider.php index 893844e2eee..a8f053f09b8 100644 --- a/src/Psalm/Internal/Provider/ParserCacheProvider.php +++ b/src/Psalm/Internal/Provider/ParserCacheProvider.php @@ -7,6 +7,7 @@ use Psalm\Config; use RuntimeException; +use function clearstatcache; use function error_log; use function fclose; use function file_get_contents; @@ -302,6 +303,13 @@ public function saveFileContentHashes(): void return; } + // directory was removed + // most likely due to a race condition with other psalm instances that were manually started at the same time + clearstatcache(true, $root_cache_directory); + if (!is_dir($root_cache_directory)) { + return; + } + $file_content_hashes = $this->new_file_content_hashes + $this->getExistingFileContentHashes(); $file_hashes_path = $root_cache_directory . DIRECTORY_SEPARATOR . self::FILE_HASHES; From 233863dc0ccaa8df983b1499beee5d1be23dc046 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Thu, 21 Jul 2022 11:36:55 +0200 Subject: [PATCH 019/178] circle CI error for unrelated code? --- src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php index cd885a0c87b..385a954c946 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php @@ -65,8 +65,6 @@ use function array_merge; use function array_search; use function array_values; -use function assert; -use function class_exists; use function count; use function end; use function in_array; From e1b0255db80b54e14405cbf581ca6d3b9b4e2e6d Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Thu, 21 Jul 2022 12:54:47 +0200 Subject: [PATCH 020/178] fix triggerErrorExits not working Fix https://github.com/vimeo/psalm/issues/8270 --- src/Psalm/Config.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index 08bc9604f2e..878d439a023 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -961,7 +961,6 @@ private static function fromXmlAndPaths( 'reportInfo' => 'report_info', 'restrictReturnTypes' => 'restrict_return_types', 'limitMethodComplexity' => 'limit_method_complexity', - 'triggerErrorExits' => 'trigger_error_exits', ]; foreach ($booleanAttributes as $xmlName => $internalName) { @@ -1096,6 +1095,13 @@ private static function fromXmlAndPaths( $config->infer_property_types_from_constructor = $attribute_text === 'true' || $attribute_text === '1'; } + if (isset($config_xml['triggerErrorExits'])) { + $attribute_text = (string) $config_xml['triggerErrorExits']; + if ($attribute_text === 'always' || $attribute_text === 'never') { + $config->trigger_error_exits = $attribute_text; + } + } + if (isset($config_xml->projectFiles)) { $config->project_files = ProjectFileFilter::loadFromXMLElement($config_xml->projectFiles, $base_dir, true); } From 8b5999470a023d985edc60c42b6f2c7df5304f15 Mon Sep 17 00:00:00 2001 From: Honca Date: Fri, 22 Jul 2022 11:09:03 +0200 Subject: [PATCH 021/178] Fixed ini_set types for arg value --- dictionaries/CallMap.php | 2 +- dictionaries/CallMap_81_delta.php | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 035e87b4409..ce827081f7d 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -6153,7 +6153,7 @@ 'ini_get' => ['string|false', 'option'=>'string'], 'ini_get_all' => ['array|false', 'extension='=>'?string', 'details='=>'bool'], 'ini_restore' => ['void', 'option'=>'string'], -'ini_set' => ['string|false', 'option'=>'string', 'value'=>'string'], +'ini_set' => ['string|false', 'option'=>'string', 'value'=>'string|int|float|bool|null'], 'inotify_add_watch' => ['int', 'inotify_instance'=>'resource', 'pathname'=>'string', 'mask'=>'int'], 'inotify_init' => ['resource|false'], 'inotify_queue_len' => ['int', 'inotify_instance'=>'resource'], diff --git a/dictionaries/CallMap_81_delta.php b/dictionaries/CallMap_81_delta.php index 5651886c22a..b6cae16f3d7 100644 --- a/dictionaries/CallMap_81_delta.php +++ b/dictionaries/CallMap_81_delta.php @@ -453,6 +453,10 @@ 'old' => ['bool', 'imap'=>'resource', 'mailbox'=>'string'], 'new' => ['bool', 'imap'=>'IMAP\Connection', 'mailbox'=>'string'], ], + 'ini_set' => [ + 'old' => ['string|false', 'option'=>'string', 'value'=>'string'], + 'new' => ['string|false', 'option'=>'string', 'value'=>'string|int|float|bool|null'], + ], 'ldap_add' => [ 'old' => ['bool', 'ldap'=>'resource', 'dn'=>'string', 'entry'=>'array', 'controls='=>'array'], 'new' => ['bool', 'ldap'=>'LDAP\Connection', 'dn'=>'string', 'entry'=>'array', 'controls='=>'?array'], From 9d3253482d78a9b6750dd9ff5eb8df858016fdc5 Mon Sep 17 00:00:00 2001 From: Semyon <7ionmail@gmail.com> Date: Fri, 22 Jul 2022 16:03:45 +0300 Subject: [PATCH 022/178] Add stub for DatePeriod --- stubs/CoreImmutableClasses.phpstub | 21 +++++++++++++++++++++ tests/CoreStubsTest.php | 30 ++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/stubs/CoreImmutableClasses.phpstub b/stubs/CoreImmutableClasses.phpstub index f9c8d6e3e34..20acef61755 100644 --- a/stubs/CoreImmutableClasses.phpstub +++ b/stubs/CoreImmutableClasses.phpstub @@ -16,6 +16,27 @@ class DateTimeZone public function __construct(string $timezone) {} } +/** + * @psalm-immutable + * + * @template-covariant From of string|DateTimeInterface + * @implements IteratorAggregate + */ +class DatePeriod implements IteratorAggregate +{ + const EXCLUDE_START_DATE = 1; + /** + * @param From $from + * @param (From is string ? 0|self::EXCLUDE_START_DATE : DateInterval) $interval_or_options + * @param (From is string ? never : DateTimeInterface|positive-int) $end_or_recurrences + * @param (From is string ? never : 0|self::EXCLUDE_START_DATE) $options + */ + public function __construct($from, $interval_or_options = 0, $end_or_recurrences = 1, $options = 0) {} + + /** @psalm-return (From is string ? (Traversable&Iterator) : (Traversable&Iterator)) */ + public function getIterator(): Iterator {} +} + /** * @psalm-taint-specialize */ diff --git a/tests/CoreStubsTest.php b/tests/CoreStubsTest.php index 8bbe6435522..38a38672c40 100644 --- a/tests/CoreStubsTest.php +++ b/tests/CoreStubsTest.php @@ -33,5 +33,35 @@ public function providerValidCodeParse(): iterable 'error_levels' => [], 'php_version' => '8.0', ]; + yield 'Iterating over \DatePeriod (#5954)' => [ + 'format("Y-m-d"); + }', + 'assertions' => [ + '$period' => 'DatePeriod', + '$dt' => 'DateTimeImmutable|null' + ], + ]; + yield 'Iterating over \DatePeriod (#5954), ISO string' => [ + 'format("Y-m-d"); + }', + 'assertions' => [ + '$period' => 'DatePeriod', + '$dt' => 'DateTime|null' + ], + ]; } } From 462ce7138ac926e832a37ed4be47b1a8ce70e1d2 Mon Sep 17 00:00:00 2001 From: Semyon <7ionmail@gmail.com> Date: Mon, 25 Jul 2022 16:37:49 +0300 Subject: [PATCH 023/178] Make DatePeriod implement Traversable oh PHP 7, rename constructor params --- stubs/CoreImmutableClasses.phpstub | 19 ++++++------- stubs/Php80.phpstub | 21 +++++++++++++++ tests/CoreStubsTest.php | 43 +++++++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/stubs/CoreImmutableClasses.phpstub b/stubs/CoreImmutableClasses.phpstub index 20acef61755..405f89b43cd 100644 --- a/stubs/CoreImmutableClasses.phpstub +++ b/stubs/CoreImmutableClasses.phpstub @@ -19,22 +19,19 @@ class DateTimeZone /** * @psalm-immutable * - * @template-covariant From of string|DateTimeInterface - * @implements IteratorAggregate + * @template-covariant Start of string|DateTimeInterface + * @implements Traversable */ -class DatePeriod implements IteratorAggregate +class DatePeriod implements Traversable { const EXCLUDE_START_DATE = 1; /** - * @param From $from - * @param (From is string ? 0|self::EXCLUDE_START_DATE : DateInterval) $interval_or_options - * @param (From is string ? never : DateTimeInterface|positive-int) $end_or_recurrences - * @param (From is string ? never : 0|self::EXCLUDE_START_DATE) $options + * @param Start $start + * @param (Start is string ? 0|self::EXCLUDE_START_DATE : DateInterval) $interval + * @param (Start is string ? never : DateTimeInterface|positive-int) $end + * @param (Start is string ? never : 0|self::EXCLUDE_START_DATE) $options */ - public function __construct($from, $interval_or_options = 0, $end_or_recurrences = 1, $options = 0) {} - - /** @psalm-return (From is string ? (Traversable&Iterator) : (Traversable&Iterator)) */ - public function getIterator(): Iterator {} + public function __construct($start, $interval = 0, $end = 1, $options = 0) {} } /** diff --git a/stubs/Php80.phpstub b/stubs/Php80.phpstub index 877b156f3a0..97b0621178b 100644 --- a/stubs/Php80.phpstub +++ b/stubs/Php80.phpstub @@ -82,3 +82,24 @@ class ReflectionUnionType extends ReflectionType { } class UnhandledMatchError extends Error {} + +/** + * @psalm-immutable + * + * @template-covariant Start of string|DateTimeInterface + * @implements IteratorAggregate + */ +class DatePeriod implements IteratorAggregate +{ + const EXCLUDE_START_DATE = 1; + /** + * @param Start $start + * @param (Start is string ? 0|self::EXCLUDE_START_DATE : DateInterval) $interval + * @param (Start is string ? never : DateTimeInterface|positive-int) $end + * @param (Start is string ? never : 0|self::EXCLUDE_START_DATE) $options + */ + public function __construct($start, $interval = 0, $end = 1, $options = 0) {} + + /** @psalm-return (Start is string ? (Traversable&Iterator) : (Traversable&Iterator)) */ + public function getIterator(): Iterator {} +} \ No newline at end of file diff --git a/tests/CoreStubsTest.php b/tests/CoreStubsTest.php index 38a38672c40..d6f0a85a916 100644 --- a/tests/CoreStubsTest.php +++ b/tests/CoreStubsTest.php @@ -33,7 +33,26 @@ public function providerValidCodeParse(): iterable 'error_levels' => [], 'php_version' => '8.0', ]; - yield 'Iterating over \DatePeriod (#5954)' => [ + yield 'Iterating over \DatePeriod (#5954) PHP7 Traversable' => [ + 'format("Y-m-d"); + }', + 'assertions' => [ + '$period' => 'DatePeriod', + '$dt' => 'DateTimeInterface|null' + ], + 'error_levels' => [], + 'php_version' => '7.3', + ]; + yield 'Iterating over \DatePeriod (#5954) PHP8 IteratorAggregate' => [ ' 'DatePeriod', '$dt' => 'DateTimeImmutable|null' ], + 'error_levels' => [], + 'php_version' => '8.0', ]; yield 'Iterating over \DatePeriod (#5954), ISO string' => [ ' 'DatePeriod', '$dt' => 'DateTime|null' ], + 'error_levels' => [], + 'php_version' => '8.0', + ]; + yield 'DatePeriod implements only Traversable on PHP 7' => [ + ' [], + 'error_levels' => [], + 'php_version' => '7.3', + ]; + yield 'DatePeriod implements IteratorAggregate on PHP 8' => [ + ' [], + 'error_levels' => ['RedundantCondition'], + 'php_version' => '8.0', ]; } } From b1295d6894f5c8308ad3c846a0e994886352450e Mon Sep 17 00:00:00 2001 From: Semyon <7ionmail@gmail.com> Date: Mon, 25 Jul 2022 17:15:28 +0300 Subject: [PATCH 024/178] Code style --- src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php | 2 -- stubs/Php80.phpstub | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php index cd885a0c87b..385a954c946 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php @@ -65,8 +65,6 @@ use function array_merge; use function array_search; use function array_values; -use function assert; -use function class_exists; use function count; use function end; use function in_array; diff --git a/stubs/Php80.phpstub b/stubs/Php80.phpstub index 97b0621178b..001ebf17ae6 100644 --- a/stubs/Php80.phpstub +++ b/stubs/Php80.phpstub @@ -102,4 +102,4 @@ class DatePeriod implements IteratorAggregate /** @psalm-return (Start is string ? (Traversable&Iterator) : (Traversable&Iterator)) */ public function getIterator(): Iterator {} -} \ No newline at end of file +} From 0c652f72f641ca5e9acf4f993627cd5c6a0aa01f Mon Sep 17 00:00:00 2001 From: someniatko Date: Fri, 29 Jul 2022 12:31:37 +0300 Subject: [PATCH 025/178] #8330 - take into account that `static` type may have been unwrapped in ExistingAtomicStaticCallAnalyzer#hasStaticInType() --- .../ExistingAtomicStaticCallAnalyzer.php | 2 +- tests/Template/ClassTemplateTest.php | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php index d7ac4ef16ff..3785023e269 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php @@ -627,7 +627,7 @@ private static function getMethodReturnType( */ private static function hasStaticInType(Type\TypeNode $type): bool { - if ($type instanceof TNamedObject && $type->value === 'static') { + if ($type instanceof TNamedObject && ($type->value === 'static' || $type->was_static)) { return true; } diff --git a/tests/Template/ClassTemplateTest.php b/tests/Template/ClassTemplateTest.php index 413f13add1f..afe14f81132 100644 --- a/tests/Template/ClassTemplateTest.php +++ b/tests/Template/ClassTemplateTest.php @@ -3799,6 +3799,33 @@ final public static function create(): static } }', ], + 'static is the return type of an analyzed static method' => [ + 'acceptA(B::create()); + } + + private function acceptA(A $_a): void + { + } + }', + ], ]; } From 0abde258fa959630b38eb1de8033bf5f91fa335b Mon Sep 17 00:00:00 2001 From: someniatko Date: Fri, 29 Jul 2022 16:50:56 +0300 Subject: [PATCH 026/178] #7731 - recognize `@psalm-allow-private-mutation` in PHP 8+ constructors --- .../Reflector/FunctionLikeNodeScanner.php | 3 ++ tests/ReadonlyPropertyTest.php | 37 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php index 969633e2e54..cc101391318 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php @@ -606,6 +606,7 @@ public function start(PhpParser\Node\FunctionLike $stmt, bool $fake_method = fal $doc_comment = $param->getDocComment(); $var_comment_type = null; $var_comment_readonly = false; + $var_comment_allow_private_mutation = false; if ($doc_comment) { $var_comments = CommentAnalyzer::getTypeFromComment( $doc_comment, @@ -620,6 +621,7 @@ public function start(PhpParser\Node\FunctionLike $stmt, bool $fake_method = fal if ($var_comment !== null) { $var_comment_type = $var_comment->type; $var_comment_readonly = $var_comment->readonly; + $var_comment_allow_private_mutation = $var_comment->allow_private_mutation; } } @@ -652,6 +654,7 @@ public function start(PhpParser\Node\FunctionLike $stmt, bool $fake_method = fal $property_storage->has_default = (bool)$param->default; $param_type_readonly = (bool)($param->flags & PhpParser\Node\Stmt\Class_::MODIFIER_READONLY); $property_storage->readonly = $param_type_readonly ?: $var_comment_readonly; + $property_storage->allow_private_mutation = $var_comment_allow_private_mutation; $param_storage->promoted_property = true; $property_storage->is_promoted = true; diff --git a/tests/ReadonlyPropertyTest.php b/tests/ReadonlyPropertyTest.php index 4cf7c4728c8..ec69276c81b 100644 --- a/tests/ReadonlyPropertyTest.php +++ b/tests/ReadonlyPropertyTest.php @@ -64,7 +64,7 @@ public function setBar(string $s) : void { echo (new A)->bar;' ], - 'readonlyPublicPropertySetInAnotherMEthod' => [ + 'readonlyPublicPropertySetInAnotherMethod' => [ 'bar;' ], + 'docblockReadonlyWithPrivateMutationsAllowedConstructorPropertySetInAnotherMethod' => [ + 'bar = $s; + } + } + + echo (new A)->bar;' + ], + 'readonlyPublicConstructorPropertySetInAnotherMethod' => [ + 'bar = $s; + } + } + + echo (new A)->bar;' + ], 'readonlyPropertySetChildClass' => [ ' Date: Fri, 29 Jul 2022 22:09:39 +0200 Subject: [PATCH 027/178] ReflectionProperty::getValue $object is nullable since php 8.0 https://www.php.net/manual/en/reflectionproperty.getvalue.php --- dictionaries/CallMap.php | 2 +- dictionaries/CallMap_80_delta.php | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index ce827081f7d..f7faaa4b875 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -11608,7 +11608,7 @@ 'ReflectionProperty::getModifiers' => ['int'], 'ReflectionProperty::getName' => ['string'], 'ReflectionProperty::getType' => ['?ReflectionType'], -'ReflectionProperty::getValue' => ['mixed', 'object='=>'object'], +'ReflectionProperty::getValue' => ['mixed', 'object='=>'null|object'], 'ReflectionProperty::hasType' => ['bool'], 'ReflectionProperty::isDefault' => ['bool'], 'ReflectionProperty::isPrivate' => ['bool'], diff --git a/dictionaries/CallMap_80_delta.php b/dictionaries/CallMap_80_delta.php index d03035cb424..afeb1bc4133 100644 --- a/dictionaries/CallMap_80_delta.php +++ b/dictionaries/CallMap_80_delta.php @@ -113,6 +113,10 @@ 'old' => ['object', 'args='=>'list'], 'new' => ['object', 'args='=>'array'], ], + 'ReflectionProperty::getValue' => [ + 'old' => ['mixed', 'object='=>'object'], + 'new' => ['mixed', 'object='=>'null|object'], + ], 'XMLWriter::flush' => [ 'old' => ['string|int|false', 'empty='=>'bool'], 'new' => ['string|int', 'empty='=>'bool'], From 90ac39d89f5257d0d6e8566a0ae3f80b02d21d0d Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 30 Jul 2022 23:31:21 +0200 Subject: [PATCH 028/178] Fix formatCurrency return type --- dictionaries/CallMap.php | 2 +- dictionaries/CallMap_historical.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index f7faaa4b875..219e6372641 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -9028,7 +9028,7 @@ 'NumberFormatter::__construct' => ['void', 'locale'=>'string', 'style'=>'int', 'pattern='=>'string'], 'NumberFormatter::create' => ['NumberFormatter|false', 'locale'=>'string', 'style'=>'int', 'pattern='=>'string'], 'NumberFormatter::format' => ['string|false', 'num'=>'', 'type='=>'int'], -'NumberFormatter::formatCurrency' => ['string', 'num'=>'float', 'currency'=>'string'], +'NumberFormatter::formatCurrency' => ['string|false', 'num'=>'float', 'currency'=>'string'], 'NumberFormatter::getAttribute' => ['int|false', 'attr'=>'int'], 'NumberFormatter::getErrorCode' => ['int'], 'NumberFormatter::getErrorMessage' => ['string'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 2c60a2ca437..a5c4875aa71 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -4420,7 +4420,7 @@ 'NumberFormatter::__construct' => ['void', 'locale'=>'string', 'style'=>'int', 'pattern='=>'string'], 'NumberFormatter::create' => ['NumberFormatter|false', 'locale'=>'string', 'style'=>'int', 'pattern='=>'string'], 'NumberFormatter::format' => ['string|false', 'num'=>'', 'type='=>'int'], - 'NumberFormatter::formatCurrency' => ['string', 'num'=>'float', 'currency'=>'string'], + 'NumberFormatter::formatCurrency' => ['string|false', 'num'=>'float', 'currency'=>'string'], 'NumberFormatter::getAttribute' => ['int|false', 'attr'=>'int'], 'NumberFormatter::getErrorCode' => ['int'], 'NumberFormatter::getErrorMessage' => ['string'], From b4b2bc66c7478e39f427114223ef7394f7b11250 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 31 Jul 2022 18:02:30 +0200 Subject: [PATCH 029/178] Added better stubs for `DateTimeImmutable`, highlighting how the constructor is **NOT** immutable `DateTimeImmutable` is **almost** immutable: `DateTimeImmutable::__construct()` is in fact not a pure method, since `new DateTimeImmutable('now')` produces a different value at each instantiation (by design). This change makes sure that `DateTimeImmutable` loses its `@psalm-immutable` class-level marker, preventing downstream misuse of the constructor inside otherwise referentially transparent code. Note: only pure methods are stubbed here: all other methods declared by `DateTimeImmutable` (parent interface) are NOT present here, and are either inferred from runtime reflection, or `CallMap*.php` definitions. Methods are sorted in the order defined by reflection on PHP 8.1.8, at the time of writing this ( https://3v4l.org/3TGg8 ). Following simplistic snippet was used to infer the current signature: ```php getName() . '(' . implode(',', array_map(function ($p) { return $p->getType() . ' $' . $p->getName() . ($p->isOptional() ? ' = ' . var_export($p->getDefaultValue(), true) : ''); }, $m->getParameters())) . ')' . ($m->getReturnType() ? (': ' . $m->getReturnType()) : ''); }, $c->getMethods()); $properties = array_map(function ($m) { return $m->getName(); }, $c->getProperties()); var_dump($methods, $properties); ``` --- stubs/CoreImmutableClasses.phpstub | 101 ++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/stubs/CoreImmutableClasses.phpstub b/stubs/CoreImmutableClasses.phpstub index 405f89b43cd..325a1c2a2ce 100644 --- a/stubs/CoreImmutableClasses.phpstub +++ b/stubs/CoreImmutableClasses.phpstub @@ -1,11 +1,106 @@ Date: Sun, 31 Jul 2022 18:08:01 +0200 Subject: [PATCH 030/178] Removed `@psalm-immutable` marked from `MyDate` extending `DateTimeImmutable` `DateTimeImmutable` is not really immutable, therefore this marker was wrong upfront --- tests/MethodCallTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/MethodCallTest.php b/tests/MethodCallTest.php index 843cce11941..956536988a1 100644 --- a/tests/MethodCallTest.php +++ b/tests/MethodCallTest.php @@ -264,7 +264,6 @@ public static function main(): void { ], 'dateTimeImmutableStatic' => [ ' Date: Sun, 31 Jul 2022 18:12:07 +0200 Subject: [PATCH 031/178] `DateTimeImmutable#sub()` always returns another `static` instance, never `false` --- tests/MethodCallTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/MethodCallTest.php b/tests/MethodCallTest.php index 956536988a1..76c033f56f5 100644 --- a/tests/MethodCallTest.php +++ b/tests/MethodCallTest.php @@ -271,7 +271,7 @@ final class MyDate extends DateTimeImmutable {} $b = (new DateTimeImmutable())->modify("+3 hours");', 'assertions' => [ - '$yesterday' => 'MyDate|false', + '$yesterday' => 'MyDate', '$b' => 'DateTimeImmutable', ], ], From 68978b9e19c70036e6e5077a6234af513afdc77f Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 1 Aug 2022 10:08:35 +0200 Subject: [PATCH 032/178] s/psalm-pure/psalm-mutation-free, since psalm-mutation-free is safer to use Ref: https://github.com/vimeo/psalm/pull/8350/files/c205d652d1e9afd9510db59e72c3fd0a4a093b3d#r934032422 The idea is that `@psalm-pure` disallows `$this` usage in child classes, which is not wanted, while `@psalm-mutation-free` allows it. By using `@psalm-mutation-free`, we don't completely destroy inheritance use-cases based on internal (immutable) state. --- stubs/CoreImmutableClasses.phpstub | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/stubs/CoreImmutableClasses.phpstub b/stubs/CoreImmutableClasses.phpstub index 325a1c2a2ce..2d6555c3df2 100644 --- a/stubs/CoreImmutableClasses.phpstub +++ b/stubs/CoreImmutableClasses.phpstub @@ -5,99 +5,99 @@ class DateTimeImmutable implements DateTimeInterface public function __construct(string $datetime = "now", DateTimeZone $timezone = null) {} /** - * @psalm-pure + * @psalm-mutation-free * @return static|false */ public static function createFromFormat(string $format, string $datetime, ?DateTimeZone $timezone = null) {} /** - * @psalm-pure + * @psalm-mutation-free * @param string $format * @return string */ public function format($format) {} /** - * @psalm-pure + * @psalm-mutation-free * @return DateTimeZone */ public function getTimezone() {} /** - * @psalm-pure + * @psalm-mutation-free * @return int */ public function getOffset() {} /** - * @psalm-pure + * @psalm-mutation-free * @return int */ public function getTimestamp() {} /** - * @psalm-pure + * @psalm-mutation-free * @param bool $absolute * @return DateInterval */ public function diff(DateTimeInterface $targetObject, $absolute = false) {} /** - * @psalm-pure + * @psalm-mutation-free * @return static */ public function modify(string $modifier) {} /** - * @psalm-pure + * @psalm-mutation-free * @return static */ public function add(DateInterval $interval) {} /** - * @psalm-pure + * @psalm-mutation-free * @return static */ public function sub(DateInterval $interval) {} /** - * @psalm-pure + * @psalm-mutation-free * @return static|false */ public function setTimezone(DateTimeZone $timezone) {} /** - * @psalm-pure + * @psalm-mutation-free * @return static|false */ public function setTime(int $hour, int $minute, int $second = 0, int $microsecond = 0) {} /** - * @psalm-pure + * @psalm-mutation-free * @return static|false */ public function setDate(int $year, int $month, int $day) {} /** - * @psalm-pure + * @psalm-mutation-free * @return static|false */ public function setISODate(int $year, int $week, int $dayOfWeek = 1) {} /** - * @psalm-pure + * @psalm-mutation-free * @return static|false */ public function setTimestamp(int $unixtimestamp) {} /** - * @psalm-pure + * @psalm-mutation-free * @return static */ public static function createFromMutable(DateTime $object) {} /** - * @psalm-pure + * @psalm-mutation-free * @return self */ public static function createFromInterface(DateTimeInterface $object) {} From 0d32203f9a94fa523bf58bb3192b20c7288aa3e3 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Wed, 3 Aug 2022 19:09:56 +0200 Subject: [PATCH 033/178] add ", but" for InvalidArgument error message where a type is provided --- .../Expression/Call/ArgumentAnalyzer.php | 14 +++++++------- .../Call/ArrayFunctionArgumentsAnalyzer.php | 12 +++++++----- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php index 84b777c7e20..32d6b4c7e3e 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php @@ -181,7 +181,7 @@ public static function checkArgumentMatches( IssueBuffer::maybeAdd( new InvalidLiteralArgument( 'Argument ' . ($argument_offset + 1) . ' of ' . $cased_method_id - . ' expects a non-literal value, ' . $arg_value_type->getId() . ' provided', + . ' expects a non-literal value, but ' . $arg_value_type->getId() . ' provided', new CodeLocation($statements_analyzer->getSource(), $arg->value), $cased_method_id ), @@ -976,7 +976,7 @@ public static function verifyType( IssueBuffer::maybeAdd( new MixedArgumentTypeCoercion( 'Argument ' . ($argument_offset + 1) . $method_identifier . ' expects ' . $param_type->getId() . - ', parent type ' . $input_type->getId() . ' provided', + ', but parent type ' . $input_type->getId() . ' provided', $arg_location, $cased_method_id, $origin_location @@ -987,7 +987,7 @@ public static function verifyType( IssueBuffer::maybeAdd( new ArgumentTypeCoercion( 'Argument ' . ($argument_offset + 1) . $method_identifier . ' expects ' . $param_type->getId() . - ', parent type ' . $input_type->getId() . ' provided', + ', but parent type ' . $input_type->getId() . ' provided', $arg_location, $cased_method_id ), @@ -1000,7 +1000,7 @@ public static function verifyType( IssueBuffer::maybeAdd( new ImplicitToStringCast( 'Argument ' . ($argument_offset + 1) . $method_identifier . ' expects ' . - $param_type->getId() . ', ' . $input_type->getId() . ' provided with a __toString method', + $param_type->getId() . ', but ' . $input_type->getId() . ' provided with a __toString method', $arg_location ), $statements_analyzer->getSuppressedIssues() @@ -1022,7 +1022,7 @@ public static function verifyType( IssueBuffer::maybeAdd( new InvalidScalarArgument( 'Argument ' . ($argument_offset + 1) . $method_identifier . ' expects ' . - $param_type->getId() . ', ' . $type . ' provided', + $param_type->getId() . ', but ' . $type . ' provided', $arg_location, $cased_method_id ), @@ -1033,7 +1033,7 @@ public static function verifyType( IssueBuffer::maybeAdd( new PossiblyInvalidArgument( 'Argument ' . ($argument_offset + 1) . $method_identifier . ' expects ' . $param_type->getId() . - ', possibly different type ' . $type . ' provided', + ', but possibly different type ' . $type . ' provided', $arg_location, $cased_method_id ), @@ -1043,7 +1043,7 @@ public static function verifyType( IssueBuffer::maybeAdd( new InvalidArgument( 'Argument ' . ($argument_offset + 1) . $method_identifier . ' expects ' . $param_type->getId() . - ', ' . $type . ' provided', + ', but ' . $type . ' provided', $arg_location, $cased_method_id ), diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php index 654e9bd829b..05c5aa91951 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php @@ -893,7 +893,8 @@ private static function checkClosureTypeArgs( IssueBuffer::maybeAdd( new MixedArgumentTypeCoercion( 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' . - $closure_param_type->getId() . ', parent type ' . $input_type->getId() . ' provided', + $closure_param_type->getId() . + ', but parent type ' . $input_type->getId() . ' provided', new CodeLocation($statements_analyzer->getSource(), $closure_arg), $method_id ), @@ -903,7 +904,8 @@ private static function checkClosureTypeArgs( IssueBuffer::maybeAdd( new ArgumentTypeCoercion( 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' . - $closure_param_type->getId() . ', parent type ' . $input_type->getId() . ' provided', + $closure_param_type->getId() . + ', but parent type ' . $input_type->getId() . ' provided', new CodeLocation($statements_analyzer->getSource(), $closure_arg), $method_id ), @@ -923,7 +925,7 @@ private static function checkClosureTypeArgs( IssueBuffer::maybeAdd( new InvalidScalarArgument( 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' . - $closure_param_type->getId() . ', ' . $input_type->getId() . ' provided', + $closure_param_type->getId() . ', but ' . $input_type->getId() . ' provided', new CodeLocation($statements_analyzer->getSource(), $closure_arg), $method_id ), @@ -933,7 +935,7 @@ private static function checkClosureTypeArgs( IssueBuffer::maybeAdd( new PossiblyInvalidArgument( 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' - . $closure_param_type->getId() . ', possibly different type ' + . $closure_param_type->getId() . ', but possibly different type ' . $input_type->getId() . ' provided', new CodeLocation($statements_analyzer->getSource(), $closure_arg), $method_id @@ -943,7 +945,7 @@ private static function checkClosureTypeArgs( } elseif (IssueBuffer::accepts( new InvalidArgument( 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' . - $closure_param_type->getId() . ', ' . $input_type->getId() . ' provided', + $closure_param_type->getId() . ', but ' . $input_type->getId() . ' provided', new CodeLocation($statements_analyzer->getSource(), $closure_arg), $method_id ), From d2be169ce50938809ccd7203eb99c9d8d4f56690 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Wed, 3 Aug 2022 19:56:38 +0200 Subject: [PATCH 034/178] update tests --- tests/ClosureTest.php | 2 +- tests/ReturnTypeTest.php | 2 +- tests/Template/ClassTemplateTest.php | 4 ++-- tests/TypeReconciliation/TypeTest.php | 4 ++-- tests/UnusedVariableTest.php | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/ClosureTest.php b/tests/ClosureTest.php index def99f9955c..cea843a3725 100644 --- a/tests/ClosureTest.php +++ b/tests/ClosureTest.php @@ -1149,7 +1149,7 @@ function takesB(B $_b) : void {} takesA($getAButReallyB()); takesB($getAButReallyB());', - 'error_message' => 'ArgumentTypeCoercion - src' . DIRECTORY_SEPARATOR . 'somefile.php:13:28 - Argument 1 of takesB expects B, parent type A provided', + 'error_message' => 'ArgumentTypeCoercion - src' . DIRECTORY_SEPARATOR . 'somefile.php:13:28 - Argument 1 of takesB expects B, but parent type A provided', ], 'closureByRefUseToMixed' => [ ' 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:13:54 - Argument 1 expects T:fn-map as mixed, int provided', + 'error_message' => 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:13:54 - Argument 1 expects T:fn-map as mixed, but int provided', ], 'cannotInferReturnClosureWithDifferentReturnTypes' => [ ' 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:20:34 - Argument 1 of type expects string, callable(State):(T:AlmostFooMap as mixed)&Foo provided', + 'error_message' => 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:20:34 - Argument 1 of type expects string, but callable(State):(T:AlmostFooMap as mixed)&Foo provided', ], 'templateWithNoReturn' => [ ' 5, "name" => "Mario", "height" => 3.5]); $mario->ame = "Luigi";', - 'error_message' => 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:47:29 - Argument 1 of CharacterRow::__set expects "height"|"id"|"name", "ame" provided', + 'error_message' => 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:47:29 - Argument 1 of CharacterRow::__set expects "height"|"id"|"name", but "ame" provided', ], 'specialiseTypeBeforeReturning' => [ ' 'ArgumentTypeCoercion - src' . DIRECTORY_SEPARATOR . 'somefile.php:14:32 - Argument 1 of takesB expects B,' - . ' parent type A&static provided', + . ' but parent type A&static provided', ], 'intersectionTypeInterfaceCheckAfterInstanceof' => [ ' 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:12:32 - Argument 1 of takesI expects I, A&static provided', + 'error_message' => 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:12:32 - Argument 1 of takesI expects I, but A&static provided', ], ]; } diff --git a/tests/UnusedVariableTest.php b/tests/UnusedVariableTest.php index 048025186ca..618f871d651 100644 --- a/tests/UnusedVariableTest.php +++ b/tests/UnusedVariableTest.php @@ -3451,7 +3451,7 @@ function takesArray($a) : void { $arr = [$a]; takesArrayOfString($arr); }', - 'error_message' => 'MixedArgumentTypeCoercion - src' . DIRECTORY_SEPARATOR . 'somefile.php:12:44 - Argument 1 of takesArrayOfString expects array, parent type array{mixed} provided. Consider improving the type at src' . DIRECTORY_SEPARATOR . 'somefile.php:10:41' + 'error_message' => 'MixedArgumentTypeCoercion - src' . DIRECTORY_SEPARATOR . 'somefile.php:12:44 - Argument 1 of takesArrayOfString expects array, but parent type array{mixed} provided. Consider improving the type at src' . DIRECTORY_SEPARATOR . 'somefile.php:10:41' ], 'warnAboutUnusedVariableInTryReassignedInCatch' => [ ' Date: Thu, 4 Aug 2022 16:50:38 +0300 Subject: [PATCH 035/178] #8363 - ensure we recognize inherited static methods for the first-class callables --- .../StaticMethod/AtomicStaticCallAnalyzer.php | 67 +++++++++++-------- tests/ClosureTest.php | 21 ++++++ 2 files changed, 61 insertions(+), 27 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php index 673dc56c505..a147c4aa37a 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php @@ -56,6 +56,7 @@ use function array_filter; use function array_map; +use function array_values; use function assert; use function count; use function in_array; @@ -369,39 +370,15 @@ private static function handleNamedCall( if (!$naive_method_exists && $codebase->methods->existence_provider->has($fq_class_name) ) { - $method_exists = $codebase->methods->existence_provider->doesMethodExist( + $fake_method_exists = $codebase->methods->existence_provider->doesMethodExist( $fq_class_name, $method_id->method_name, $statements_analyzer, null - ); - - if ($method_exists) { - $fake_method_exists = true; - } - } - - if ($stmt->isFirstClassCallable()) { - $method_storage = ($class_storage->methods[$method_name_lc] ?? - ($class_storage->pseudo_static_methods[$method_name_lc] ?? null)); - - if ($method_storage) { - $return_type_candidate = new Union([new TClosure( - 'Closure', - $method_storage->params, - $method_storage->return_type, - $method_storage->pure - )]); - } else { - $return_type_candidate = Type::getClosure(); - } - - $statements_analyzer->node_data->setType($stmt, $return_type_candidate); - - return true; + ) ?? false; } - $args = $stmt->getArgs(); + $args = $stmt->isFirstClassCallable() ? [] : $stmt->getArgs(); if (!$naive_method_exists && $class_storage->mixin_declaring_fqcln @@ -504,6 +481,42 @@ private static function handleNamedCall( $method_name_lc ); + if ($stmt->isFirstClassCallable()) { + if ($found_method_and_class_storage) { + [ $method_storage ] = $found_method_and_class_storage; + + $return_type_candidate = new Union([new TClosure( + 'Closure', + $method_storage->params, + $method_storage->return_type, + $method_storage->pure + )]); + } else { + $method_exists = $naive_method_exists + || $fake_method_exists + || isset($class_storage->methods[$method_name_lc]) + || isset($class_storage->pseudo_static_methods[$method_name_lc]); + + if ($method_exists) { + $declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id) ?? $method_id; + + $return_type_candidate = new Union([new TClosure( + 'Closure', + array_values($codebase->getMethodParams($method_id)), + $codebase->getMethodReturnType($method_id, $fq_class_name), + $codebase->methods->getStorage($declaring_method_id)->pure + )]); + } else { + // FIXME: perhaps Psalm should complain about nonexisting method here, or throw a logic exception? + $return_type_candidate = Type::getClosure(); + } + } + + $statements_analyzer->node_data->setType($stmt, $return_type_candidate); + + return true; + } + if (!$naive_method_exists || !MethodAnalyzer::isMethodVisible( $method_id, diff --git a/tests/ClosureTest.php b/tests/ClosureTest.php index cea843a3725..e6e5699600e 100644 --- a/tests/ClosureTest.php +++ b/tests/ClosureTest.php @@ -751,6 +751,27 @@ public static function __callStatic(string $name, array $args): mixed { [], '8.1' ], + 'FirstClassCallable:OverriddenStaticMethod' => [ + ' [], + [], + '8.1', + ], 'FirstClassCallable:WithArrayMap' => [ ' Date: Thu, 4 Aug 2022 17:16:06 +0300 Subject: [PATCH 036/178] #8363 - support `static` as a type parameter in return types of the first-class callables --- .../StaticMethod/AtomicStaticCallAnalyzer.php | 13 +++++++- tests/ClosureTest.php | 30 ++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php index a147c4aa37a..a4c33c24c08 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php @@ -512,7 +512,18 @@ private static function handleNamedCall( } } - $statements_analyzer->node_data->setType($stmt, $return_type_candidate); + $expanded_return_type = TypeExpander::expandUnion( + $codebase, + $return_type_candidate, + $context->self, + $class_storage->name, + $context->parent, + true, + false, + true + ); + + $statements_analyzer->node_data->setType($stmt, $expanded_return_type); return true; } diff --git a/tests/ClosureTest.php b/tests/ClosureTest.php index e6e5699600e..53e5cc3f9b2 100644 --- a/tests/ClosureTest.php +++ b/tests/ClosureTest.php @@ -751,7 +751,7 @@ public static function __callStatic(string $name, array $args): mixed { [], '8.1' ], - 'FirstClassCallable:OverriddenStaticMethod' => [ + 'FirstClassCallable:InheritedStaticMethod' => [ ' [ + ' */ + public static function create(int $i): Holder + { + return new Holder(new static($i)); + } + } + + class C extends A {} + + /** @param \Closure(int):Holder $_ */ + function takesIntToHolder(\Closure $_): void {} + + takesIntToHolder(C::create(...));' + ], 'FirstClassCallable:WithArrayMap' => [ ' Date: Fri, 5 Aug 2022 12:22:27 +0200 Subject: [PATCH 037/178] Removed `DateTimeImmutable::__construct` from the CallMap: fully covered by stub --- dictionaries/CallMap.php | 2 -- dictionaries/CallMap_historical.php | 2 -- 2 files changed, 4 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 219e6372641..b34c6c59cb1 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1793,8 +1793,6 @@ 'DateTime::setTimestamp' => ['static', 'unixtimestamp'=>'int'], 'DateTime::setTimezone' => ['static', 'timezone'=>'DateTimeZone'], 'DateTime::sub' => ['static', 'interval'=>'DateInterval'], -'DateTimeImmutable::__construct' => ['void', 'time='=>'string'], -'DateTimeImmutable::__construct\'1' => ['void', 'time'=>'?string', 'timezone'=>'?DateTimeZone'], 'DateTimeImmutable::__set_state' => ['static', 'array'=>'array'], 'DateTimeImmutable::__wakeup' => ['void'], 'DateTimeImmutable::add' => ['static', 'interval'=>'DateInterval'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index a5c4875aa71..ebce06c0070 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1055,8 +1055,6 @@ 'DateTime::setTimestamp' => ['static', 'unixtimestamp'=>'int'], 'DateTime::setTimezone' => ['static', 'timezone'=>'DateTimeZone'], 'DateTime::sub' => ['static', 'interval'=>'DateInterval'], - 'DateTimeImmutable::__construct' => ['void', 'time='=>'string'], - 'DateTimeImmutable::__construct\'1' => ['void', 'time'=>'?string', 'timezone'=>'?DateTimeZone'], 'DateTimeImmutable::__set_state' => ['static', 'array'=>'array'], 'DateTimeImmutable::__wakeup' => ['void'], 'DateTimeImmutable::add' => ['static', 'interval'=>'DateInterval'], From 267d76088d7bbdc19c31bbc7615fc3e5bebed1bc Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 5 Aug 2022 12:23:00 +0200 Subject: [PATCH 038/178] Removed `DateTimeImmutable::sub()` from the CallMap: fully covered by stub --- dictionaries/CallMap.php | 1 - dictionaries/CallMap_historical.php | 1 - stubs/CoreImmutableClasses.phpstub | 5 ++++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index b34c6c59cb1..7870727ba9f 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1811,7 +1811,6 @@ 'DateTimeImmutable::setTime' => ['static|false', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], 'DateTimeImmutable::setTimestamp' => ['static|false', 'unixtimestamp'=>'int'], 'DateTimeImmutable::setTimezone' => ['static|false', 'timezone'=>'DateTimeZone'], -'DateTimeImmutable::sub' => ['static|false', 'interval'=>'DateInterval'], 'DateTimeInterface::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeInterface::format' => ['string', 'format'=>'string'], 'DateTimeInterface::getOffset' => ['int'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index ebce06c0070..d387582cb51 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1072,7 +1072,6 @@ 'DateTimeImmutable::setTime' => ['static|false', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], 'DateTimeImmutable::setTimestamp' => ['static|false', 'unixtimestamp'=>'int'], 'DateTimeImmutable::setTimezone' => ['static|false', 'timezone'=>'DateTimeZone'], - 'DateTimeImmutable::sub' => ['static|false', 'interval'=>'DateInterval'], 'DateTimeInterface::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeInterface::format' => ['string', 'format'=>'string'], 'DateTimeInterface::getOffset' => ['int'], diff --git a/stubs/CoreImmutableClasses.phpstub b/stubs/CoreImmutableClasses.phpstub index 2d6555c3df2..e5db68d978f 100644 --- a/stubs/CoreImmutableClasses.phpstub +++ b/stubs/CoreImmutableClasses.phpstub @@ -56,7 +56,10 @@ class DateTimeImmutable implements DateTimeInterface /** * @psalm-mutation-free - * @return static + * @return static|false this method can fail in case an {@see DateInterval} with relative + * week days is passed in. + * + * @see https://github.com/php/php-src/blob/534127d3b22b193ffb9511c4447584f0d2bd4e24/ext/date/php_date.c#L3157-L3160 */ public function sub(DateInterval $interval) {} From 58ca4e0b73eb52c6b171ef5b59f776e70cc4288b Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 5 Aug 2022 12:23:43 +0200 Subject: [PATCH 039/178] Removed `DateTimeImmutable::createFromFormat()` from the CallMap: fully covered by stub --- dictionaries/CallMap.php | 1 - dictionaries/CallMap_historical.php | 1 - 2 files changed, 2 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 7870727ba9f..20f68f13e05 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1796,7 +1796,6 @@ 'DateTimeImmutable::__set_state' => ['static', 'array'=>'array'], 'DateTimeImmutable::__wakeup' => ['void'], 'DateTimeImmutable::add' => ['static', 'interval'=>'DateInterval'], -'DateTimeImmutable::createFromFormat' => ['static|false', 'format'=>'string', 'time'=>'string', 'timezone='=>'?DateTimeZone'], 'DateTimeImmutable::createFromInterface' => ['self', 'object' => 'DateTimeInterface'], 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index d387582cb51..d0fc4bc2b02 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1058,7 +1058,6 @@ 'DateTimeImmutable::__set_state' => ['static', 'array'=>'array'], 'DateTimeImmutable::__wakeup' => ['void'], 'DateTimeImmutable::add' => ['static', 'interval'=>'DateInterval'], - 'DateTimeImmutable::createFromFormat' => ['static|false', 'format'=>'string', 'time'=>'string', 'timezone='=>'?DateTimeZone'], 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeImmutable::format' => ['string|false', 'format'=>'string'], From 7ee12c74935dabe2b481a322acc02ba87217b049 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 5 Aug 2022 12:24:35 +0200 Subject: [PATCH 040/178] Removed `DateTimeImmutable::format()` from the CallMap: fully covered by stub Note: some conditional return type magic was required here. See: https://github.com/vimeo/psalm/pull/8350#discussion_r937089212 --- dictionaries/CallMap.php | 1 - dictionaries/CallMap_80_delta.php | 4 ---- dictionaries/CallMap_historical.php | 1 - stubs/CoreImmutableClasses.phpstub | 4 +++- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 20f68f13e05..40633b8e18c 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1799,7 +1799,6 @@ 'DateTimeImmutable::createFromInterface' => ['self', 'object' => 'DateTimeInterface'], 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], -'DateTimeImmutable::format' => ['string', 'format'=>'string'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], 'DateTimeImmutable::getOffset' => ['int'], 'DateTimeImmutable::getTimestamp' => ['int|false'], diff --git a/dictionaries/CallMap_80_delta.php b/dictionaries/CallMap_80_delta.php index afeb1bc4133..4200608e06a 100644 --- a/dictionaries/CallMap_80_delta.php +++ b/dictionaries/CallMap_80_delta.php @@ -41,10 +41,6 @@ 'old' => ['string|false', 'format'=>'string'], 'new' => ['string', 'format'=>'string'], ], - 'DateTimeImmutable::format' => [ - 'old' => ['string|false', 'format'=>'string'], - 'new' => ['string', 'format'=>'string'], - ], 'DateTimeZone::listIdentifiers' => [ 'old' => ['list|false', 'timezoneGroup='=>'int', 'countryCode='=>'string|null'], 'new' => ['list', 'timezoneGroup='=>'int', 'countryCode='=>'string|null'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index d0fc4bc2b02..013d88e0071 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1060,7 +1060,6 @@ 'DateTimeImmutable::add' => ['static', 'interval'=>'DateInterval'], 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], - 'DateTimeImmutable::format' => ['string|false', 'format'=>'string'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], 'DateTimeImmutable::getOffset' => ['int'], 'DateTimeImmutable::getTimestamp' => ['int|false'], diff --git a/stubs/CoreImmutableClasses.phpstub b/stubs/CoreImmutableClasses.phpstub index e5db68d978f..c241cb4f17a 100644 --- a/stubs/CoreImmutableClasses.phpstub +++ b/stubs/CoreImmutableClasses.phpstub @@ -12,8 +12,10 @@ class DateTimeImmutable implements DateTimeInterface /** * @psalm-mutation-free + * * @param string $format - * @return string + * + * @return (\PHP_MAJOR_VERSION is int<0, 7> ? string|false : string) */ public function format($format) {} From 2b6fddf88d5408da418f808a75bb497b6886e186 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 5 Aug 2022 12:26:59 +0200 Subject: [PATCH 041/178] Removed `DateTimeImmutable::getTimezone()` from the CallMap: fully covered by stub Note: also verified that a `DateTimeImmutable#getTimezone()` always returns a default timezone (initialized internally), and therefore restricted the type a bit. --- dictionaries/CallMap.php | 1 - dictionaries/CallMap_historical.php | 1 - 2 files changed, 2 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 40633b8e18c..52f26dba263 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1802,7 +1802,6 @@ 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], 'DateTimeImmutable::getOffset' => ['int'], 'DateTimeImmutable::getTimestamp' => ['int|false'], -'DateTimeImmutable::getTimezone' => ['DateTimeZone|false'], 'DateTimeImmutable::modify' => ['static', 'modify'=>'string'], 'DateTimeImmutable::setDate' => ['static|false', 'year'=>'int', 'month'=>'int', 'day'=>'int'], 'DateTimeImmutable::setISODate' => ['static|false', 'year'=>'int', 'week'=>'int', 'day='=>'int'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 013d88e0071..89193d9804e 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1063,7 +1063,6 @@ 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], 'DateTimeImmutable::getOffset' => ['int'], 'DateTimeImmutable::getTimestamp' => ['int|false'], - 'DateTimeImmutable::getTimezone' => ['DateTimeZone|false'], 'DateTimeImmutable::modify' => ['static', 'modify'=>'string'], 'DateTimeImmutable::setDate' => ['static|false', 'year'=>'int', 'month'=>'int', 'day'=>'int'], 'DateTimeImmutable::setISODate' => ['static|false', 'year'=>'int', 'week'=>'int', 'day='=>'int'], From 1be04e0988286950cf34fb02ce60caad89c02be6 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 5 Aug 2022 12:27:29 +0200 Subject: [PATCH 042/178] Removed `DateTimeImmutable::getOffset()` from the CallMap: fully covered by stub --- dictionaries/CallMap.php | 1 - dictionaries/CallMap_historical.php | 1 - 2 files changed, 2 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 52f26dba263..e1c4f97da8d 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1800,7 +1800,6 @@ 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], -'DateTimeImmutable::getOffset' => ['int'], 'DateTimeImmutable::getTimestamp' => ['int|false'], 'DateTimeImmutable::modify' => ['static', 'modify'=>'string'], 'DateTimeImmutable::setDate' => ['static|false', 'year'=>'int', 'month'=>'int', 'day'=>'int'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 89193d9804e..52c0d8c2dac 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1061,7 +1061,6 @@ 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], - 'DateTimeImmutable::getOffset' => ['int'], 'DateTimeImmutable::getTimestamp' => ['int|false'], 'DateTimeImmutable::modify' => ['static', 'modify'=>'string'], 'DateTimeImmutable::setDate' => ['static|false', 'year'=>'int', 'month'=>'int', 'day'=>'int'], From 002585b57ec014a40cf81f14cedc58936c1ddfd8 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 5 Aug 2022 12:32:26 +0200 Subject: [PATCH 043/178] Removed `DateTimeImmutable::getTimestamp()` from the CallMap: fully covered by stub This also simplifies the return type from `int|false` to always `int`, since a timestamp can always be produced. Ref: https://github.com/php/php-src/blob/eff9aed1592f59cddb12d36a55dec0ccc3bbbfd6/ext/date/php_date.stub.php#L496-L500 --- dictionaries/CallMap.php | 1 - dictionaries/CallMap_historical.php | 1 - 2 files changed, 2 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index e1c4f97da8d..651fdc08f85 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1800,7 +1800,6 @@ 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], -'DateTimeImmutable::getTimestamp' => ['int|false'], 'DateTimeImmutable::modify' => ['static', 'modify'=>'string'], 'DateTimeImmutable::setDate' => ['static|false', 'year'=>'int', 'month'=>'int', 'day'=>'int'], 'DateTimeImmutable::setISODate' => ['static|false', 'year'=>'int', 'week'=>'int', 'day='=>'int'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 52c0d8c2dac..98c7d172b0b 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1061,7 +1061,6 @@ 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], - 'DateTimeImmutable::getTimestamp' => ['int|false'], 'DateTimeImmutable::modify' => ['static', 'modify'=>'string'], 'DateTimeImmutable::setDate' => ['static|false', 'year'=>'int', 'month'=>'int', 'day'=>'int'], 'DateTimeImmutable::setISODate' => ['static|false', 'year'=>'int', 'week'=>'int', 'day='=>'int'], From 18557b8c7005a85ab7f6ffefbf0b6683a11ea4d2 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 5 Aug 2022 12:34:31 +0200 Subject: [PATCH 044/178] Removed `DateTimeImmutable::diff()` from the CallMap: fully covered by stub --- dictionaries/CallMap.php | 1 - dictionaries/CallMap_historical.php | 1 - 2 files changed, 2 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 651fdc08f85..dc136691866 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1798,7 +1798,6 @@ 'DateTimeImmutable::add' => ['static', 'interval'=>'DateInterval'], 'DateTimeImmutable::createFromInterface' => ['self', 'object' => 'DateTimeInterface'], 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], -'DateTimeImmutable::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], 'DateTimeImmutable::modify' => ['static', 'modify'=>'string'], 'DateTimeImmutable::setDate' => ['static|false', 'year'=>'int', 'month'=>'int', 'day'=>'int'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 98c7d172b0b..9e96e514e0e 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1059,7 +1059,6 @@ 'DateTimeImmutable::__wakeup' => ['void'], 'DateTimeImmutable::add' => ['static', 'interval'=>'DateInterval'], 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], - 'DateTimeImmutable::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], 'DateTimeImmutable::modify' => ['static', 'modify'=>'string'], 'DateTimeImmutable::setDate' => ['static|false', 'year'=>'int', 'month'=>'int', 'day'=>'int'], From 7cd3d49dc4de625b7b96a1c99c7c7f269acf6725 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 5 Aug 2022 12:37:24 +0200 Subject: [PATCH 045/178] Removed `DateTimeImmutable::modify()` from the CallMap: fully covered by stub Also expanded the return type from `static` to `static|false`, since the operation can fail (with a warning too), such as in following example: https://3v4l.org/Xrjlc ```php modify('potato') ); ``` Produces ``` Warning: DateTimeImmutable::modify(): Failed to parse time string (potato) at position 0 (p): The timezone could not be found in the database in /in/Xrjlc on line 6 bool(false) ``` Ref: https://github.com/php/php-src/blob/534127d3b22b193ffb9511c4447584f0d2bd4e24/ext/date/php_date.stub.php#L508-L509 --- dictionaries/CallMap.php | 1 - dictionaries/CallMap_historical.php | 1 - stubs/CoreImmutableClasses.phpstub | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index dc136691866..b6b9fe20581 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1799,7 +1799,6 @@ 'DateTimeImmutable::createFromInterface' => ['self', 'object' => 'DateTimeInterface'], 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], -'DateTimeImmutable::modify' => ['static', 'modify'=>'string'], 'DateTimeImmutable::setDate' => ['static|false', 'year'=>'int', 'month'=>'int', 'day'=>'int'], 'DateTimeImmutable::setISODate' => ['static|false', 'year'=>'int', 'week'=>'int', 'day='=>'int'], 'DateTimeImmutable::setTime' => ['static|false', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 9e96e514e0e..cda731c378e 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1060,7 +1060,6 @@ 'DateTimeImmutable::add' => ['static', 'interval'=>'DateInterval'], 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], - 'DateTimeImmutable::modify' => ['static', 'modify'=>'string'], 'DateTimeImmutable::setDate' => ['static|false', 'year'=>'int', 'month'=>'int', 'day'=>'int'], 'DateTimeImmutable::setISODate' => ['static|false', 'year'=>'int', 'week'=>'int', 'day='=>'int'], 'DateTimeImmutable::setTime' => ['static|false', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], diff --git a/stubs/CoreImmutableClasses.phpstub b/stubs/CoreImmutableClasses.phpstub index c241cb4f17a..4e9cf0df3e9 100644 --- a/stubs/CoreImmutableClasses.phpstub +++ b/stubs/CoreImmutableClasses.phpstub @@ -46,7 +46,7 @@ class DateTimeImmutable implements DateTimeInterface /** * @psalm-mutation-free - * @return static + * @return static|false */ public function modify(string $modifier) {} From cb9939cbd37c9e7285d243716d666004af98023b Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 5 Aug 2022 12:39:16 +0200 Subject: [PATCH 046/178] Removed `DateTimeImmutable::add()` from the CallMap: fully covered by stub Ref: https://github.com/php/php-src/blob/534127d3b22b193ffb9511c4447584f0d2bd4e24/ext/date/php_date.stub.php#L511-L512 Ref: https://github.com/php/php-src/blob/534127d3b22b193ffb9511c4447584f0d2bd4e24/ext/date/php_date.c#L3129-L3144 --- dictionaries/CallMap.php | 1 - dictionaries/CallMap_historical.php | 1 - 2 files changed, 2 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index b6b9fe20581..809204d57f2 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1795,7 +1795,6 @@ 'DateTime::sub' => ['static', 'interval'=>'DateInterval'], 'DateTimeImmutable::__set_state' => ['static', 'array'=>'array'], 'DateTimeImmutable::__wakeup' => ['void'], -'DateTimeImmutable::add' => ['static', 'interval'=>'DateInterval'], 'DateTimeImmutable::createFromInterface' => ['self', 'object' => 'DateTimeInterface'], 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index cda731c378e..801026e0d18 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1057,7 +1057,6 @@ 'DateTime::sub' => ['static', 'interval'=>'DateInterval'], 'DateTimeImmutable::__set_state' => ['static', 'array'=>'array'], 'DateTimeImmutable::__wakeup' => ['void'], - 'DateTimeImmutable::add' => ['static', 'interval'=>'DateInterval'], 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], 'DateTimeImmutable::setDate' => ['static|false', 'year'=>'int', 'month'=>'int', 'day'=>'int'], From 4fe554d6d227e49fcdf41d2bbd8b64b3a058aa63 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 5 Aug 2022 12:42:45 +0200 Subject: [PATCH 047/178] Removed `DateTimeImmutable::setTimezone()` from the CallMap: fully covered by stub Also simplified the return type from `static|false` to `static`, since the method throws at all times, on failure. On PHP 7.x, it could only fail if an invalid type was passed in, which is not really valid anyway, from a type perspective. Ref (PHP 8.2.x): https://github.com/php/php-src/blob/534127d3b22b193ffb9511c4447584f0d2bd4e24/ext/date/php_date.c#L3291-L3307 Ref (PHP 8.2.x): https://github.com/php/php-src/blob/534127d3b22b193ffb9511c4447584f0d2bd4e24/ext/date/php_date.stub.php#L517-L518 Ref (PHP 7.0.33): https://github.com/php/php-src/blob/bf574c2b67a1f786e36cf679f41b758b973a82c4/ext/date/php_date.c#L3363-L3379 --- dictionaries/CallMap.php | 1 - dictionaries/CallMap_historical.php | 1 - stubs/CoreImmutableClasses.phpstub | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 809204d57f2..0da0f76ecb9 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1802,7 +1802,6 @@ 'DateTimeImmutable::setISODate' => ['static|false', 'year'=>'int', 'week'=>'int', 'day='=>'int'], 'DateTimeImmutable::setTime' => ['static|false', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], 'DateTimeImmutable::setTimestamp' => ['static|false', 'unixtimestamp'=>'int'], -'DateTimeImmutable::setTimezone' => ['static|false', 'timezone'=>'DateTimeZone'], 'DateTimeInterface::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeInterface::format' => ['string', 'format'=>'string'], 'DateTimeInterface::getOffset' => ['int'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 801026e0d18..8af7c4a8165 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1063,7 +1063,6 @@ 'DateTimeImmutable::setISODate' => ['static|false', 'year'=>'int', 'week'=>'int', 'day='=>'int'], 'DateTimeImmutable::setTime' => ['static|false', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], 'DateTimeImmutable::setTimestamp' => ['static|false', 'unixtimestamp'=>'int'], - 'DateTimeImmutable::setTimezone' => ['static|false', 'timezone'=>'DateTimeZone'], 'DateTimeInterface::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeInterface::format' => ['string', 'format'=>'string'], 'DateTimeInterface::getOffset' => ['int'], diff --git a/stubs/CoreImmutableClasses.phpstub b/stubs/CoreImmutableClasses.phpstub index 4e9cf0df3e9..21efbef48e3 100644 --- a/stubs/CoreImmutableClasses.phpstub +++ b/stubs/CoreImmutableClasses.phpstub @@ -67,7 +67,7 @@ class DateTimeImmutable implements DateTimeInterface /** * @psalm-mutation-free - * @return static|false + * @return static */ public function setTimezone(DateTimeZone $timezone) {} From e61c593a2c3cadbd656ff6768e302ee94e287da2 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 5 Aug 2022 12:47:04 +0200 Subject: [PATCH 048/178] Removed `DateTimeImmutable::setTime()` from the CallMap: fully covered by stub Also simplified the return type from `static|false` to `static`, since the method throws at all times, on failure. On PHP 7.x, it could only fail if an invalid type was passed in, which is not really valid anyway, from a type perspective. Ref (PHP 8.1.x): https://github.com/php/php-src/blob/32d55f74229e7913db0d59ef874a401744479b6a/ext/date/php_date.c#L3212-L3228 Ref (PHP 7.0.33): https://github.com/php/php-src/blob/bf574c2b67a1f786e36cf679f41b758b973a82c4/ext/date/php_date.c#L3447-L3463 --- dictionaries/CallMap.php | 1 - dictionaries/CallMap_historical.php | 1 - stubs/CoreImmutableClasses.phpstub | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 0da0f76ecb9..86da052b3b6 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1800,7 +1800,6 @@ 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], 'DateTimeImmutable::setDate' => ['static|false', 'year'=>'int', 'month'=>'int', 'day'=>'int'], 'DateTimeImmutable::setISODate' => ['static|false', 'year'=>'int', 'week'=>'int', 'day='=>'int'], -'DateTimeImmutable::setTime' => ['static|false', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], 'DateTimeImmutable::setTimestamp' => ['static|false', 'unixtimestamp'=>'int'], 'DateTimeInterface::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeInterface::format' => ['string', 'format'=>'string'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 8af7c4a8165..176ffce3868 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1061,7 +1061,6 @@ 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], 'DateTimeImmutable::setDate' => ['static|false', 'year'=>'int', 'month'=>'int', 'day'=>'int'], 'DateTimeImmutable::setISODate' => ['static|false', 'year'=>'int', 'week'=>'int', 'day='=>'int'], - 'DateTimeImmutable::setTime' => ['static|false', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], 'DateTimeImmutable::setTimestamp' => ['static|false', 'unixtimestamp'=>'int'], 'DateTimeInterface::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeInterface::format' => ['string', 'format'=>'string'], diff --git a/stubs/CoreImmutableClasses.phpstub b/stubs/CoreImmutableClasses.phpstub index 21efbef48e3..aab2e880641 100644 --- a/stubs/CoreImmutableClasses.phpstub +++ b/stubs/CoreImmutableClasses.phpstub @@ -73,7 +73,7 @@ class DateTimeImmutable implements DateTimeInterface /** * @psalm-mutation-free - * @return static|false + * @return static */ public function setTime(int $hour, int $minute, int $second = 0, int $microsecond = 0) {} From 0a6c9d01776a99ab759062aa1b03c445550633d4 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 5 Aug 2022 12:48:29 +0200 Subject: [PATCH 049/178] Removed `DateTimeImmutable::setDate()` from the CallMap: fully covered by stub Also simplified the return type from `static|false` to `static`, since the method throws at all times, on failure. On PHP 7.x, it could only fail if an invalid type was passed in, which is not really valid anyway, from a type perspective. Ref (PHP 8.1.x): https://github.com/php/php-src/blob/32d55f74229e7913db0d59ef874a401744479b6a/ext/date/php_date.c#L3258-L3274 Ref (PHP 7.0.33): https://github.com/php/php-src/blob/bf574c2b67a1f786e36cf679f41b758b973a82c4/ext/date/php_date.c#L3496-L3512 --- dictionaries/CallMap.php | 1 - dictionaries/CallMap_historical.php | 1 - stubs/CoreImmutableClasses.phpstub | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 86da052b3b6..630770d728d 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1798,7 +1798,6 @@ 'DateTimeImmutable::createFromInterface' => ['self', 'object' => 'DateTimeInterface'], 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], -'DateTimeImmutable::setDate' => ['static|false', 'year'=>'int', 'month'=>'int', 'day'=>'int'], 'DateTimeImmutable::setISODate' => ['static|false', 'year'=>'int', 'week'=>'int', 'day='=>'int'], 'DateTimeImmutable::setTimestamp' => ['static|false', 'unixtimestamp'=>'int'], 'DateTimeInterface::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 176ffce3868..9fd8252b8a0 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1059,7 +1059,6 @@ 'DateTimeImmutable::__wakeup' => ['void'], 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], - 'DateTimeImmutable::setDate' => ['static|false', 'year'=>'int', 'month'=>'int', 'day'=>'int'], 'DateTimeImmutable::setISODate' => ['static|false', 'year'=>'int', 'week'=>'int', 'day='=>'int'], 'DateTimeImmutable::setTimestamp' => ['static|false', 'unixtimestamp'=>'int'], 'DateTimeInterface::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], diff --git a/stubs/CoreImmutableClasses.phpstub b/stubs/CoreImmutableClasses.phpstub index aab2e880641..fcdae2705d3 100644 --- a/stubs/CoreImmutableClasses.phpstub +++ b/stubs/CoreImmutableClasses.phpstub @@ -79,7 +79,7 @@ class DateTimeImmutable implements DateTimeInterface /** * @psalm-mutation-free - * @return static|false + * @return static */ public function setDate(int $year, int $month, int $day) {} From 964f64a500b987c82bd5274d07d3b67e1d0b381b Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 5 Aug 2022 12:49:45 +0200 Subject: [PATCH 050/178] Removed `DateTimeImmutable::setISODate()` from the CallMap: fully covered by stub Also simplified the return type from `static|false` to `static`, since the method throws at all times, on failure. On PHP 7.x, it could only fail if an invalid type was passed in, which is not really valid anyway, from a type perspective. Ref (PHP 8.1.x): https://github.com/php/php-src/blob/32d55f74229e7913db0d59ef874a401744479b6a/ext/date/php_date.c#L3308-L3324 Ref (PHP 7.0.33): https://github.com/php/php-src/blob/bf574c2b67a1f786e36cf679f41b758b973a82c4/ext/date/php_date.c#L3549-L3565 --- dictionaries/CallMap.php | 1 - dictionaries/CallMap_historical.php | 1 - stubs/CoreImmutableClasses.phpstub | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 630770d728d..28171d694a4 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1798,7 +1798,6 @@ 'DateTimeImmutable::createFromInterface' => ['self', 'object' => 'DateTimeInterface'], 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], -'DateTimeImmutable::setISODate' => ['static|false', 'year'=>'int', 'week'=>'int', 'day='=>'int'], 'DateTimeImmutable::setTimestamp' => ['static|false', 'unixtimestamp'=>'int'], 'DateTimeInterface::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeInterface::format' => ['string', 'format'=>'string'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 9fd8252b8a0..c15978b7e50 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1059,7 +1059,6 @@ 'DateTimeImmutable::__wakeup' => ['void'], 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], - 'DateTimeImmutable::setISODate' => ['static|false', 'year'=>'int', 'week'=>'int', 'day='=>'int'], 'DateTimeImmutable::setTimestamp' => ['static|false', 'unixtimestamp'=>'int'], 'DateTimeInterface::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeInterface::format' => ['string', 'format'=>'string'], diff --git a/stubs/CoreImmutableClasses.phpstub b/stubs/CoreImmutableClasses.phpstub index fcdae2705d3..1829ad4e891 100644 --- a/stubs/CoreImmutableClasses.phpstub +++ b/stubs/CoreImmutableClasses.phpstub @@ -85,7 +85,7 @@ class DateTimeImmutable implements DateTimeInterface /** * @psalm-mutation-free - * @return static|false + * @return static */ public function setISODate(int $year, int $week, int $dayOfWeek = 1) {} From aaac9ccb90f32739dd36cdea7bcc16ca9e30e71a Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 5 Aug 2022 12:50:51 +0200 Subject: [PATCH 051/178] Removed `DateTimeImmutable::setTimestamp()` from the CallMap: fully covered by stub Also simplified the return type from `static|false` to `static`, since the method throws at all times, on failure. On PHP 7.x, it could only fail if an invalid type was passed in, which is not really valid anyway, from a type perspective. Ref (PHP 8.1.x): https://github.com/php/php-src/blob/32d55f74229e7913db0d59ef874a401744479b6a/ext/date/php_date.c#L3353-L3369 Ref (PHP 7.0.33): https://github.com/php/php-src/blob/bf574c2b67a1f786e36cf679f41b758b973a82c4/ext/date/php_date.c#L3596-L3612 --- dictionaries/CallMap.php | 1 - dictionaries/CallMap_historical.php | 1 - stubs/CoreImmutableClasses.phpstub | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 28171d694a4..caf3cfaf5f6 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1798,7 +1798,6 @@ 'DateTimeImmutable::createFromInterface' => ['self', 'object' => 'DateTimeInterface'], 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], -'DateTimeImmutable::setTimestamp' => ['static|false', 'unixtimestamp'=>'int'], 'DateTimeInterface::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeInterface::format' => ['string', 'format'=>'string'], 'DateTimeInterface::getOffset' => ['int'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index c15978b7e50..4fa58db7dc2 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1059,7 +1059,6 @@ 'DateTimeImmutable::__wakeup' => ['void'], 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], - 'DateTimeImmutable::setTimestamp' => ['static|false', 'unixtimestamp'=>'int'], 'DateTimeInterface::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeInterface::format' => ['string', 'format'=>'string'], 'DateTimeInterface::getOffset' => ['int'], diff --git a/stubs/CoreImmutableClasses.phpstub b/stubs/CoreImmutableClasses.phpstub index 1829ad4e891..acf7fc0bf39 100644 --- a/stubs/CoreImmutableClasses.phpstub +++ b/stubs/CoreImmutableClasses.phpstub @@ -91,7 +91,7 @@ class DateTimeImmutable implements DateTimeInterface /** * @psalm-mutation-free - * @return static|false + * @return static */ public function setTimestamp(int $unixtimestamp) {} From a1ed84f1ed7fb8a030fea09aa2e6b4780903b3d7 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 5 Aug 2022 12:52:32 +0200 Subject: [PATCH 052/178] Removed `DateTimeImmutable::createFromMutable()` from the CallMap: fully covered by stub --- dictionaries/CallMap.php | 1 - dictionaries/CallMap_historical.php | 1 - 2 files changed, 2 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index caf3cfaf5f6..329decd3234 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1796,7 +1796,6 @@ 'DateTimeImmutable::__set_state' => ['static', 'array'=>'array'], 'DateTimeImmutable::__wakeup' => ['void'], 'DateTimeImmutable::createFromInterface' => ['self', 'object' => 'DateTimeInterface'], -'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], 'DateTimeInterface::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeInterface::format' => ['string', 'format'=>'string'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 4fa58db7dc2..6df5d473acf 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1057,7 +1057,6 @@ 'DateTime::sub' => ['static', 'interval'=>'DateInterval'], 'DateTimeImmutable::__set_state' => ['static', 'array'=>'array'], 'DateTimeImmutable::__wakeup' => ['void'], - 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], 'DateTimeInterface::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeInterface::format' => ['string', 'format'=>'string'], From 68ffae097e32d64397179da1b7353f7e4f0cdea7 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 5 Aug 2022 13:21:28 +0200 Subject: [PATCH 053/178] Simplified `DateTimeImmutable::format()`: always returns a `string` Also: * a non-empty format string will always lead to `non-empty-string` * it seems that you can throw **everything** at `DateTimeInterface#format()`, even null bytes, and it will always produce a `string` --- stubs/CoreImmutableClasses.phpstub | 2 +- tests/MethodCallTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/stubs/CoreImmutableClasses.phpstub b/stubs/CoreImmutableClasses.phpstub index acf7fc0bf39..3a537ba4575 100644 --- a/stubs/CoreImmutableClasses.phpstub +++ b/stubs/CoreImmutableClasses.phpstub @@ -15,7 +15,7 @@ class DateTimeImmutable implements DateTimeInterface * * @param string $format * - * @return (\PHP_MAJOR_VERSION is int<0, 7> ? string|false : string) + * @return ($format is non-empty-string ? non-empty-string : string) */ public function format($format) {} diff --git a/tests/MethodCallTest.php b/tests/MethodCallTest.php index 76c033f56f5..62860e8f209 100644 --- a/tests/MethodCallTest.php +++ b/tests/MethodCallTest.php @@ -271,8 +271,8 @@ final class MyDate extends DateTimeImmutable {} $b = (new DateTimeImmutable())->modify("+3 hours");', 'assertions' => [ - '$yesterday' => 'MyDate', - '$b' => 'DateTimeImmutable', + '$yesterday' => 'MyDate|false', + '$b' => 'DateTimeImmutable|false', ], ], 'magicCall' => [ From 13828771c7c544aa61a3db1ef49b7aae9e0996fd Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 5 Aug 2022 13:28:53 +0200 Subject: [PATCH 054/178] Removed `DateTimeImmutable::createFromInterface()` from stubs While there is value in declaring `DateTimeImmutable::createFromInterface()` as mutation-free in a stub, this method was introduced in PHP 8.0, so we cannot really declare it in a stub. For now, we drop it, as the value of its stub declaration is much lower than the problems it introduces through its conditional existence. --- stubs/CoreImmutableClasses.phpstub | 6 ------ 1 file changed, 6 deletions(-) diff --git a/stubs/CoreImmutableClasses.phpstub b/stubs/CoreImmutableClasses.phpstub index 3a537ba4575..23eb77bdb77 100644 --- a/stubs/CoreImmutableClasses.phpstub +++ b/stubs/CoreImmutableClasses.phpstub @@ -100,12 +100,6 @@ class DateTimeImmutable implements DateTimeInterface * @return static */ public static function createFromMutable(DateTime $object) {} - - /** - * @psalm-mutation-free - * @return self - */ - public static function createFromInterface(DateTimeInterface $object) {} } /** From fefd4861d6641e2104972a3d3fc22670b2eed41d Mon Sep 17 00:00:00 2001 From: Teemu Koskinen Date: Sat, 6 Aug 2022 02:27:01 +0300 Subject: [PATCH 055/178] Use $codebase->classlike_storage_provider only if it has the required data. Fixes #8373 --- src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php b/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php index 7d6dbf6cee3..c09b16e7089 100644 --- a/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php +++ b/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php @@ -1138,7 +1138,7 @@ public static function getMappedGenericTypeParams( ): array { if ($input_type_part instanceof TGenericObject || $input_type_part instanceof TIterable) { $input_type_params = $input_type_part->type_params; - } else { + } elseif ($codebase->classlike_storage_provider->has($input_type_part->value)) { $class_storage = $codebase->classlike_storage_provider->get($input_type_part->value); $container_class = $container_type_part->value; @@ -1150,6 +1150,8 @@ public static function getMappedGenericTypeParams( } else { $input_type_params = array_fill(0, count($class_storage->template_types ?? []), Type::getMixed()); } + } else { + $input_type_params = []; } try { From 89b7b3234bf8b4fabf154b0594726b6b1c137602 Mon Sep 17 00:00:00 2001 From: Teemu Koskinen Date: Mon, 8 Aug 2022 23:47:11 +0300 Subject: [PATCH 056/178] Add test for #8373 Undefined classes in function dockblocks should not crash psalm. Test provided by @AndrolGenhald --- tests/Template/ClassTemplateTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Template/ClassTemplateTest.php b/tests/Template/ClassTemplateTest.php index b76c93c3e05..ee84dbce884 100644 --- a/tests/Template/ClassTemplateTest.php +++ b/tests/Template/ClassTemplateTest.php @@ -3826,6 +3826,22 @@ private function acceptA(A $_a): void } }', ], + 'undefined class in function dockblock' => [ + ' $baz + */ + function foobar(DoesNotExist $baz): void {} + + /** + * @psalm-suppress UndefinedDocblockClass, UndefinedClass + * @var DoesNotExist + */ + $baz = new DoesNotExist(); + foobar($baz);', + ], ]; } From 8ca594a34d1fc307f55bbf0549d7b8912573cd90 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Fri, 5 Aug 2022 11:05:08 +0200 Subject: [PATCH 057/178] always use lock when writing/reading cache data to/from file --- .../ClassLikeStorageCacheProvider.php | 11 +- .../Provider/FileReferenceCacheProvider.php | 142 +++++++++--------- .../Provider/FileStorageCacheProvider.php | 11 +- .../Internal/Provider/ParserCacheProvider.php | 49 ++---- src/Psalm/Internal/Provider/Providers.php | 45 ++++++ 5 files changed, 139 insertions(+), 119 deletions(-) diff --git a/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php b/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php index dae227f5520..c7fa19acd0c 100644 --- a/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php +++ b/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php @@ -3,13 +3,13 @@ namespace Psalm\Internal\Provider; use Psalm\Config; +use Psalm\Internal\Provider\Providers; use Psalm\Storage\ClassLikeStorage; use UnexpectedValueException; use function array_merge; use function dirname; use function file_exists; -use function file_get_contents; use function file_put_contents; use function filemtime; use function get_class; @@ -25,6 +25,7 @@ use function unserialize; use const DIRECTORY_SEPARATOR; +use const LOCK_EX; use const PHP_VERSION_ID; /** @@ -86,9 +87,9 @@ public function writeToCache(ClassLikeStorage $storage, string $file_path, strin $cache_location = $this->getCacheLocationForClass($fq_classlike_name_lc, $file_path, true); if ($this->config->use_igbinary) { - file_put_contents($cache_location, igbinary_serialize($storage)); + file_put_contents($cache_location, igbinary_serialize($storage), LOCK_EX); } else { - file_put_contents($cache_location, serialize($storage)); + file_put_contents($cache_location, serialize($storage), LOCK_EX); } } @@ -132,7 +133,7 @@ private function loadFromCache(string $fq_classlike_name_lc, ?string $file_path) if (file_exists($cache_location)) { if ($this->config->use_igbinary) { - $storage = igbinary_unserialize((string)file_get_contents($cache_location)); + $storage = igbinary_unserialize(Providers::safeFileGetContents($cache_location)); if ($storage instanceof ClassLikeStorage) { return $storage; @@ -141,7 +142,7 @@ private function loadFromCache(string $fq_classlike_name_lc, ?string $file_path) return null; } - $storage = unserialize((string)file_get_contents($cache_location)); + $storage = unserialize(Providers::safeFileGetContents($cache_location)); if ($storage instanceof ClassLikeStorage) { return $storage; diff --git a/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php b/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php index 616a1c2c4e0..e512aacf29b 100644 --- a/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php +++ b/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php @@ -4,10 +4,10 @@ use Psalm\Config; use Psalm\Internal\Codebase\Analyzer; +use Psalm\Internal\Provider\Providers; use UnexpectedValueException; use function file_exists; -use function file_get_contents; use function file_put_contents; use function igbinary_serialize; use function igbinary_unserialize; @@ -18,6 +18,7 @@ use function unserialize; use const DIRECTORY_SEPARATOR; +use const LOCK_EX; /** * @psalm-import-type FileMapType from Analyzer @@ -84,9 +85,9 @@ public function getCachedFileReferences(): ?array } if ($this->config->use_igbinary) { - $reference_cache = igbinary_unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = igbinary_unserialize(Providers::safeFileGetContents($reference_cache_location)); } else { - $reference_cache = unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = unserialize(Providers::safeFileGetContents($reference_cache_location)); } if (!is_array($reference_cache)) { @@ -114,9 +115,9 @@ public function getCachedClassLikeFiles(): ?array } if ($this->config->use_igbinary) { - $reference_cache = igbinary_unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = igbinary_unserialize(Providers::safeFileGetContents($reference_cache_location)); } else { - $reference_cache = unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = unserialize(Providers::safeFileGetContents($reference_cache_location)); } if (!is_array($reference_cache)) { @@ -144,9 +145,9 @@ public function getCachedNonMethodClassReferences(): ?array } if ($this->config->use_igbinary) { - $reference_cache = igbinary_unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = igbinary_unserialize(Providers::safeFileGetContents($reference_cache_location)); } else { - $reference_cache = unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = unserialize(Providers::safeFileGetContents($reference_cache_location)); } if (!is_array($reference_cache)) { @@ -174,9 +175,9 @@ public function getCachedMethodClassReferences(): ?array } if ($this->config->use_igbinary) { - $reference_cache = igbinary_unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = igbinary_unserialize(Providers::safeFileGetContents($reference_cache_location)); } else { - $reference_cache = unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = unserialize(Providers::safeFileGetContents($reference_cache_location)); } if (!is_array($reference_cache)) { @@ -203,7 +204,7 @@ public function getCachedMethodMemberReferences(): ?array return null; } - $class_member_reference_cache = (string) file_get_contents($class_member_cache_location); + $class_member_reference_cache = Providers::safeFileGetContents($class_member_cache_location); if ($this->config->use_igbinary) { $class_member_reference_cache = igbinary_unserialize($class_member_reference_cache); } else { @@ -235,7 +236,7 @@ public function getCachedMethodDependencies(): ?array return null; } - $method_dependencies_cache = (string) file_get_contents($method_dependencies_cache_location); + $method_dependencies_cache = Providers::safeFileGetContents($method_dependencies_cache_location); if ($this->config->use_igbinary) { $method_dependencies_cache = igbinary_unserialize($method_dependencies_cache); } else { @@ -266,7 +267,7 @@ public function getCachedMethodPropertyReferences(): ?array return null; } - $class_member_reference_cache = (string) file_get_contents($class_member_cache_location); + $class_member_reference_cache = Providers::safeFileGetContents($class_member_cache_location); if ($this->config->use_igbinary) { $class_member_reference_cache = igbinary_unserialize($class_member_reference_cache); } else { @@ -297,7 +298,7 @@ public function getCachedMethodMethodReturnReferences(): ?array return null; } - $class_member_reference_cache = (string) file_get_contents($class_member_cache_location); + $class_member_reference_cache = Providers::safeFileGetContents($class_member_cache_location); if ($this->config->use_igbinary) { $class_member_reference_cache = igbinary_unserialize($class_member_reference_cache); } else { @@ -328,7 +329,7 @@ public function getCachedMethodMissingMemberReferences(): ?array return null; } - $class_member_reference_cache = (string) file_get_contents($class_member_cache_location); + $class_member_reference_cache = Providers::safeFileGetContents($class_member_cache_location); if ($this->config->use_igbinary) { $class_member_reference_cache = igbinary_unserialize($class_member_reference_cache); } else { @@ -359,7 +360,7 @@ public function getCachedFileMemberReferences(): ?array return null; } - $file_class_member_reference_cache = (string) file_get_contents($file_class_member_cache_location); + $file_class_member_reference_cache = Providers::safeFileGetContents($file_class_member_cache_location); if ($this->config->use_igbinary) { $file_class_member_reference_cache = igbinary_unserialize($file_class_member_reference_cache); } else { @@ -392,7 +393,7 @@ public function getCachedFilePropertyReferences(): ?array return null; } - $file_class_member_reference_cache = (string) file_get_contents($file_class_member_cache_location); + $file_class_member_reference_cache = Providers::safeFileGetContents($file_class_member_cache_location); if ($this->config->use_igbinary) { $file_class_member_reference_cache = igbinary_unserialize($file_class_member_reference_cache); } else { @@ -425,7 +426,7 @@ public function getCachedFileMethodReturnReferences(): ?array return null; } - $file_class_member_reference_cache = (string) file_get_contents($file_class_member_cache_location); + $file_class_member_reference_cache = Providers::safeFileGetContents($file_class_member_cache_location); if ($this->config->use_igbinary) { $file_class_member_reference_cache = igbinary_unserialize($file_class_member_reference_cache); } else { @@ -457,7 +458,7 @@ public function getCachedFileMissingMemberReferences(): ?array return null; } - $file_class_member_reference_cache = (string) file_get_contents($file_class_member_cache_location); + $file_class_member_reference_cache = Providers::safeFileGetContents($file_class_member_cache_location); if ($this->config->use_igbinary) { $file_class_member_reference_cache = igbinary_unserialize($file_class_member_reference_cache); } else { @@ -489,9 +490,9 @@ public function getCachedMixedMemberNameReferences(): ?array } if ($this->config->use_igbinary) { - $reference_cache = igbinary_unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = igbinary_unserialize(Providers::safeFileGetContents($reference_cache_location)); } else { - $reference_cache = unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = unserialize(Providers::safeFileGetContents($reference_cache_location)); } if (!is_array($reference_cache)) { @@ -519,9 +520,9 @@ public function getCachedMethodParamUses(): ?array } if ($this->config->use_igbinary) { - $reference_cache = igbinary_unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = igbinary_unserialize(Providers::safeFileGetContents($reference_cache_location)); } else { - $reference_cache = unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = unserialize(Providers::safeFileGetContents($reference_cache_location)); } if (!is_array($reference_cache)) { @@ -549,9 +550,9 @@ public function getCachedIssues(): ?array } if ($this->config->use_igbinary) { - $issues_cache = igbinary_unserialize((string) file_get_contents($issues_cache_location)); + $issues_cache = igbinary_unserialize(Providers::safeFileGetContents($issues_cache_location)); } else { - $issues_cache = unserialize((string) file_get_contents($issues_cache_location)); + $issues_cache = unserialize(Providers::safeFileGetContents($issues_cache_location)); } if (!is_array($issues_cache)) { @@ -572,9 +573,9 @@ public function setCachedFileReferences(array $file_references): void $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)); + file_put_contents($reference_cache_location, igbinary_serialize($file_references), LOCK_EX); } else { - file_put_contents($reference_cache_location, serialize($file_references)); + file_put_contents($reference_cache_location, serialize($file_references), LOCK_EX); } } @@ -589,9 +590,9 @@ public function setCachedClassLikeFiles(array $file_references): void $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)); + file_put_contents($reference_cache_location, igbinary_serialize($file_references), LOCK_EX); } else { - file_put_contents($reference_cache_location, serialize($file_references)); + file_put_contents($reference_cache_location, serialize($file_references), LOCK_EX); } } @@ -606,9 +607,9 @@ public function setCachedNonMethodClassReferences(array $file_class_references): $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)); + file_put_contents($reference_cache_location, igbinary_serialize($file_class_references), LOCK_EX); } else { - file_put_contents($reference_cache_location, serialize($file_class_references)); + file_put_contents($reference_cache_location, serialize($file_class_references), LOCK_EX); } } @@ -623,9 +624,9 @@ public function setCachedMethodClassReferences(array $method_class_references): $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)); + file_put_contents($reference_cache_location, igbinary_serialize($method_class_references), LOCK_EX); } else { - file_put_contents($reference_cache_location, serialize($method_class_references)); + file_put_contents($reference_cache_location, serialize($method_class_references), LOCK_EX); } } @@ -640,9 +641,9 @@ public function setCachedMethodMemberReferences(array $member_references): void $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)); + file_put_contents($member_cache_location, igbinary_serialize($member_references), LOCK_EX); } else { - file_put_contents($member_cache_location, serialize($member_references)); + file_put_contents($member_cache_location, serialize($member_references), LOCK_EX); } } @@ -657,9 +658,9 @@ public function setCachedMethodDependencies(array $member_references): void $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)); + file_put_contents($member_cache_location, igbinary_serialize($member_references), LOCK_EX); } else { - file_put_contents($member_cache_location, serialize($member_references)); + file_put_contents($member_cache_location, serialize($member_references), LOCK_EX); } } @@ -674,9 +675,9 @@ public function setCachedMethodPropertyReferences(array $property_references): v $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)); + file_put_contents($member_cache_location, igbinary_serialize($property_references), LOCK_EX); } else { - file_put_contents($member_cache_location, serialize($property_references)); + file_put_contents($member_cache_location, serialize($property_references), LOCK_EX); } } @@ -691,9 +692,9 @@ public function setCachedMethodMethodReturnReferences(array $method_return_refer $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)); + file_put_contents($member_cache_location, igbinary_serialize($method_return_references), LOCK_EX); } else { - file_put_contents($member_cache_location, serialize($method_return_references)); + file_put_contents($member_cache_location, serialize($method_return_references), LOCK_EX); } } @@ -708,9 +709,9 @@ public function setCachedMethodMissingMemberReferences(array $member_references) $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)); + file_put_contents($member_cache_location, igbinary_serialize($member_references), LOCK_EX); } else { - file_put_contents($member_cache_location, serialize($member_references)); + file_put_contents($member_cache_location, serialize($member_references), LOCK_EX); } } @@ -725,9 +726,9 @@ public function setCachedFileMemberReferences(array $member_references): void $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)); + file_put_contents($member_cache_location, igbinary_serialize($member_references), LOCK_EX); } else { - file_put_contents($member_cache_location, serialize($member_references)); + file_put_contents($member_cache_location, serialize($member_references), LOCK_EX); } } @@ -742,9 +743,9 @@ public function setCachedFilePropertyReferences(array $property_references): voi $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)); + file_put_contents($member_cache_location, igbinary_serialize($property_references), LOCK_EX); } else { - file_put_contents($member_cache_location, serialize($property_references)); + file_put_contents($member_cache_location, serialize($property_references), LOCK_EX); } } @@ -759,9 +760,9 @@ public function setCachedFileMethodReturnReferences(array $method_return_referen $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)); + file_put_contents($member_cache_location, igbinary_serialize($method_return_references), LOCK_EX); } else { - file_put_contents($member_cache_location, serialize($method_return_references)); + file_put_contents($member_cache_location, serialize($method_return_references), LOCK_EX); } } @@ -776,9 +777,9 @@ public function setCachedFileMissingMemberReferences(array $member_references): $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)); + file_put_contents($member_cache_location, igbinary_serialize($member_references), LOCK_EX); } else { - file_put_contents($member_cache_location, serialize($member_references)); + file_put_contents($member_cache_location, serialize($member_references), LOCK_EX); } } @@ -793,9 +794,9 @@ public function setCachedMixedMemberNameReferences(array $references): void $reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::UNKNOWN_MEMBER_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($reference_cache_location, igbinary_serialize($references)); + file_put_contents($reference_cache_location, igbinary_serialize($references), LOCK_EX); } else { - file_put_contents($reference_cache_location, serialize($references)); + file_put_contents($reference_cache_location, serialize($references), LOCK_EX); } } @@ -810,9 +811,9 @@ public function setCachedMethodParamUses(array $uses): void $reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_PARAM_USE_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($reference_cache_location, igbinary_serialize($uses)); + file_put_contents($reference_cache_location, igbinary_serialize($uses), LOCK_EX); } else { - file_put_contents($reference_cache_location, serialize($uses)); + file_put_contents($reference_cache_location, serialize($uses), LOCK_EX); } } @@ -827,9 +828,9 @@ public function setCachedIssues(array $issues): void $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)); + file_put_contents($issues_cache_location, igbinary_serialize($issues), LOCK_EX); } else { - file_put_contents($issues_cache_location, serialize($issues)); + file_put_contents($issues_cache_location, serialize($issues), LOCK_EX); } } @@ -847,10 +848,10 @@ public function getAnalyzedMethodCache() ) { if ($this->config->use_igbinary) { /** @var array> */ - return igbinary_unserialize(file_get_contents($analyzed_methods_cache_location)); + return igbinary_unserialize(Providers::safeFileGetContents($analyzed_methods_cache_location)); } else { /** @var array> */ - return unserialize(file_get_contents($analyzed_methods_cache_location)); + return unserialize(Providers::safeFileGetContents($analyzed_methods_cache_location)); } } @@ -870,9 +871,9 @@ public function setAnalyzedMethodCache(array $analyzed_methods): void . self::ANALYZED_METHODS_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($analyzed_methods_cache_location, igbinary_serialize($analyzed_methods)); + file_put_contents($analyzed_methods_cache_location, igbinary_serialize($analyzed_methods), LOCK_EX); } else { - file_put_contents($analyzed_methods_cache_location, serialize($analyzed_methods)); + file_put_contents($analyzed_methods_cache_location, serialize($analyzed_methods), LOCK_EX); } } } @@ -893,12 +894,12 @@ public function getFileMapCache() /** * @var array */ - $file_maps_cache = igbinary_unserialize(file_get_contents($file_maps_cache_location)); + $file_maps_cache = igbinary_unserialize(Providers::safeFileGetContents($file_maps_cache_location)); } else { /** * @var array */ - $file_maps_cache = unserialize(file_get_contents($file_maps_cache_location)); + $file_maps_cache = unserialize(Providers::safeFileGetContents($file_maps_cache_location)); } return $file_maps_cache; @@ -918,9 +919,9 @@ public function setFileMapCache(array $file_maps): void $file_maps_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::FILE_MAPS_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($file_maps_cache_location, igbinary_serialize($file_maps)); + file_put_contents($file_maps_cache_location, igbinary_serialize($file_maps), LOCK_EX); } else { - file_put_contents($file_maps_cache_location, serialize($file_maps)); + file_put_contents($file_maps_cache_location, serialize($file_maps), LOCK_EX); } } } @@ -939,10 +940,10 @@ public function getTypeCoverage() ) { if ($this->config->use_igbinary) { /** @var array */ - $type_coverage_cache = igbinary_unserialize(file_get_contents($type_coverage_cache_location)); + $type_coverage_cache = igbinary_unserialize(Providers::safeFileGetContents($type_coverage_cache_location)); } else { /** @var array */ - $type_coverage_cache = unserialize(file_get_contents($type_coverage_cache_location)); + $type_coverage_cache = unserialize(Providers::safeFileGetContents($type_coverage_cache_location)); } return $type_coverage_cache; @@ -962,9 +963,9 @@ public function setTypeCoverage(array $mixed_counts): void $type_coverage_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::TYPE_COVERAGE_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($type_coverage_cache_location, igbinary_serialize($mixed_counts)); + file_put_contents($type_coverage_cache_location, igbinary_serialize($mixed_counts), LOCK_EX); } else { - file_put_contents($type_coverage_cache_location, serialize($mixed_counts)); + file_put_contents($type_coverage_cache_location, serialize($mixed_counts), LOCK_EX); } } } @@ -981,7 +982,7 @@ public function getConfigHashCache() if ($cache_directory && file_exists($config_hash_cache_location) ) { - return file_get_contents($config_hash_cache_location); + return Providers::safeFileGetContents($config_hash_cache_location); } return false; @@ -1000,7 +1001,8 @@ public function setConfigHashCache(string $hash): void file_put_contents( $config_hash_cache_location, - $hash + $hash, + LOCK_EX ); } } diff --git a/src/Psalm/Internal/Provider/FileStorageCacheProvider.php b/src/Psalm/Internal/Provider/FileStorageCacheProvider.php index d52b4165acf..2f9279f12ae 100644 --- a/src/Psalm/Internal/Provider/FileStorageCacheProvider.php +++ b/src/Psalm/Internal/Provider/FileStorageCacheProvider.php @@ -3,13 +3,13 @@ namespace Psalm\Internal\Provider; use Psalm\Config; +use Psalm\Internal\Provider\Providers; use Psalm\Storage\FileStorage; use UnexpectedValueException; use function array_merge; use function dirname; use function file_exists; -use function file_get_contents; use function file_put_contents; use function filemtime; use function get_class; @@ -24,6 +24,7 @@ use function unserialize; use const DIRECTORY_SEPARATOR; +use const LOCK_EX; use const PHP_VERSION_ID; /** @@ -79,9 +80,9 @@ public function writeToCache(FileStorage $storage, string $file_contents): void $storage->hash = $this->getCacheHash($file_path, $file_contents); if ($this->config->use_igbinary) { - file_put_contents($cache_location, igbinary_serialize($storage)); + file_put_contents($cache_location, igbinary_serialize($storage), LOCK_EX); } else { - file_put_contents($cache_location, serialize($storage)); + file_put_contents($cache_location, serialize($storage), LOCK_EX); } } @@ -135,7 +136,7 @@ private function loadFromCache(string $file_path): ?FileStorage if (file_exists($cache_location)) { if ($this->config->use_igbinary) { - $storage = igbinary_unserialize((string)file_get_contents($cache_location)); + $storage = igbinary_unserialize(Providers::safeFileGetContents($cache_location)); if ($storage instanceof FileStorage) { return $storage; @@ -144,7 +145,7 @@ private function loadFromCache(string $file_path): ?FileStorage return null; } - $storage = unserialize((string)file_get_contents($cache_location)); + $storage = unserialize(Providers::safeFileGetContents($cache_location)); if ($storage instanceof FileStorage) { return $storage; diff --git a/src/Psalm/Internal/Provider/ParserCacheProvider.php b/src/Psalm/Internal/Provider/ParserCacheProvider.php index a8f053f09b8..c1c30c27b76 100644 --- a/src/Psalm/Internal/Provider/ParserCacheProvider.php +++ b/src/Psalm/Internal/Provider/ParserCacheProvider.php @@ -5,18 +5,13 @@ use PhpParser; use PhpParser\Node\Stmt; use Psalm\Config; +use Psalm\Internal\Provider\Providers; use RuntimeException; use function clearstatcache; use function error_log; -use function fclose; -use function file_get_contents; use function file_put_contents; use function filemtime; -use function filesize; -use function flock; -use function fopen; -use function fread; use function gettype; use function igbinary_serialize; use function igbinary_unserialize; @@ -33,11 +28,9 @@ use function touch; use function unlink; use function unserialize; -use function usleep; use const DIRECTORY_SEPARATOR; use const LOCK_EX; -use const LOCK_SH; use const SCANDIR_SORT_NONE; /** @@ -108,10 +101,10 @@ public function loadStatementsFromCache( ) { if ($this->use_igbinary) { /** @var list */ - $stmts = igbinary_unserialize((string)file_get_contents($cache_location)); + $stmts = igbinary_unserialize(Providers::safeFileGetContents($cache_location)); } else { /** @var list */ - $stmts = unserialize((string)file_get_contents($cache_location)); + $stmts = unserialize(Providers::safeFileGetContents($cache_location)); } return $stmts; @@ -142,11 +135,11 @@ public function loadExistingStatementsFromCache(string $file_path): ?array if (is_readable($cache_location)) { if ($this->use_igbinary) { /** @var list */ - return igbinary_unserialize((string)file_get_contents($cache_location)) ?: null; + return igbinary_unserialize(Providers::safeFileGetContents($cache_location)) ?: null; } /** @var list */ - return unserialize((string)file_get_contents($cache_location)) ?: null; + return unserialize(Providers::safeFileGetContents($cache_location)) ?: null; } return null; @@ -173,7 +166,7 @@ public function loadExistingFileContentsFromCache(string $file_path): ?string $cache_location = $parser_cache_directory . DIRECTORY_SEPARATOR . $file_cache_key; if (is_readable($cache_location)) { - return file_get_contents($cache_location); + return Providers::safeFileGetContents($cache_location); } return null; @@ -191,29 +184,7 @@ private function getExistingFileContentHashes(): array $file_hashes_path = $root_cache_directory . DIRECTORY_SEPARATOR . self::FILE_HASHES; if ($root_cache_directory && is_readable($file_hashes_path)) { - $fp = fopen($file_hashes_path, 'r'); - $max_wait_cycles = 5; - $has_lock = false; - while ($max_wait_cycles > 0) { - if (flock($fp, LOCK_SH)) { - $has_lock = true; - break; - } - $max_wait_cycles--; - usleep(50000); - } - - if (!$has_lock) { - fclose($fp); - error_log('Could not acquire lock for content hashes file'); - $this->existing_file_content_hashes = []; - - return []; - } - - $hashes_encoded = fread($fp, filesize($file_hashes_path)); - fclose($fp); - + $hashes_encoded = Providers::safeFileGetContents($file_hashes_path); if (!$hashes_encoded) { error_log('Unexpected value when loading from file content hashes'); $this->existing_file_content_hashes = []; @@ -269,9 +240,9 @@ public function saveStatementsToCache( $this->createCacheDirectory($parser_cache_directory); if ($this->use_igbinary) { - file_put_contents($cache_location, igbinary_serialize($stmts)); + file_put_contents($cache_location, igbinary_serialize($stmts), LOCK_EX); } else { - file_put_contents($cache_location, serialize($stmts)); + file_put_contents($cache_location, serialize($stmts), LOCK_EX); } $this->new_file_content_hashes[$file_cache_key] = $file_content_hash; @@ -343,7 +314,7 @@ public function cacheFileContents(string $file_path, string $file_contents): voi $this->createCacheDirectory($parser_cache_directory); - file_put_contents($cache_location, $file_contents); + file_put_contents($cache_location, $file_contents, LOCK_EX); } public function deleteOldParserCaches(float $time_before): int diff --git a/src/Psalm/Internal/Provider/Providers.php b/src/Psalm/Internal/Provider/Providers.php index d42e1004342..8d246b2d1f9 100644 --- a/src/Psalm/Internal/Provider/Providers.php +++ b/src/Psalm/Internal/Provider/Providers.php @@ -2,6 +2,17 @@ namespace Psalm\Internal\Provider; +use RuntimeException; + +use function fclose; +use function filesize; +use function flock; +use function fopen; +use function fread; +use function usleep; + +use const LOCK_SH; + /** * @internal */ @@ -63,4 +74,38 @@ public function __construct( ); $this->file_reference_provider = new FileReferenceProvider($file_reference_cache_provider); } + + public static function safeFileGetContents(string $path): string + { + // no readable validation as that must be done in the caller + $fp = fopen($path, 'r'); + if ($fp === false) { + return ''; + } + $max_wait_cycles = 5; + $has_lock = false; + while ($max_wait_cycles > 0) { + if (flock($fp, LOCK_SH)) { + $has_lock = true; + break; + } + $max_wait_cycles--; + usleep(50000); + } + + if (!$has_lock) { + fclose($fp); + throw new RuntimeException('Could not acquire lock for ' . $path); + } + + $file_size = filesize($path); + $content = ''; + if ( $file_size > 0 ) { + $content = (string) fread($fp, $file_size); + } + + fclose($fp); + + return $content; + } } From 8f6e16add645fa08070d1ec7d909a974a98715cb Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 11 Aug 2022 16:48:29 +0200 Subject: [PATCH 058/178] added truthy-string alias for non-falsy-string --- src/Psalm/Type/Atomic.php | 1 + tests/TypeCombinationTest.php | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php index 47d8e573ade..b0d456b65dd 100644 --- a/src/Psalm/Type/Atomic.php +++ b/src/Psalm/Type/Atomic.php @@ -210,6 +210,7 @@ public static function create( case 'non-empty-string': return new TNonEmptyString(); + case 'truthy-string': case 'non-falsy-string': return new TNonFalsyString(); diff --git a/tests/TypeCombinationTest.php b/tests/TypeCombinationTest.php index 1f3350f3bfb..60a857b6cfe 100644 --- a/tests/TypeCombinationTest.php +++ b/tests/TypeCombinationTest.php @@ -717,6 +717,13 @@ public function providerTestValidTypeCombination(): array 'non-empty-string' ] ], + 'combineTruthyStringAndNonEmptyString' => [ + 'non-empty-string', + [ + 'truthy-string', + 'non-empty-string' + ] + ], 'combineNonFalsyNonEmptyString' => [ 'non-empty-string', [ From 89086382197f0fd47fbfc58d85b9beb3cf63ecec Mon Sep 17 00:00:00 2001 From: Paul Fedorow Date: Fri, 12 Aug 2022 11:08:28 +0200 Subject: [PATCH 059/178] Fix `imageinterlace` function signature --- dictionaries/CallMap.php | 2 +- dictionaries/CallMap_80_delta.php | 2 +- dictionaries/CallMap_81_delta.php | 4 ++++ tests/Internal/Codebase/InternalCallMapHandlerTest.php | 1 - 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 329decd3234..143d7d9a189 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -5410,7 +5410,7 @@ 'imagegif' => ['bool', 'image'=>'GdImage', 'file='=>'string|resource|null'], 'imagegrabscreen' => ['false|GdImage'], 'imagegrabwindow' => ['false|GdImage', 'handle'=>'int', 'client_area='=>'int'], -'imageinterlace' => ['int|false', 'image'=>'GdImage', 'enable='=>'int'], +'imageinterlace' => ['bool', 'image'=>'GdImage', 'enable='=>'bool|null'], 'imageistruecolor' => ['bool', 'image'=>'GdImage'], 'imagejpeg' => ['bool', 'image'=>'GdImage', 'file='=>'string|resource|null', 'quality='=>'int'], 'imagelayereffect' => ['bool', 'image'=>'GdImage', 'effect'=>'int'], diff --git a/dictionaries/CallMap_80_delta.php b/dictionaries/CallMap_80_delta.php index 4200608e06a..39edf984020 100644 --- a/dictionaries/CallMap_80_delta.php +++ b/dictionaries/CallMap_80_delta.php @@ -647,7 +647,7 @@ ], 'imageinterlace' => [ 'old' => ['int|false', 'image'=>'resource', 'enable='=>'int'], - 'new' => ['int|false', 'image'=>'GdImage', 'enable='=>'int'], + 'new' => ['int|bool', 'image'=>'GdImage', 'enable='=>'bool|null'], ], 'imageistruecolor' => [ 'old' => ['bool', 'image'=>'resource'], diff --git a/dictionaries/CallMap_81_delta.php b/dictionaries/CallMap_81_delta.php index b6cae16f3d7..622c86614d0 100644 --- a/dictionaries/CallMap_81_delta.php +++ b/dictionaries/CallMap_81_delta.php @@ -233,6 +233,10 @@ 'old' => ['HashContext', 'algo'=>'string', 'flags='=>'int', 'key='=>'string'], 'new' => ['HashContext', 'algo'=>'string', 'flags='=>'int', 'key='=>'string', 'options='=>'array'], ], + 'imageinterlace' => [ + 'old' => ['int|bool', 'image'=>'GdImage', 'enable='=>'bool|null'], + 'new' => ['bool', 'image'=>'GdImage', 'enable='=>'bool|null'], + ], 'imap_append' => [ 'old' => ['bool', 'imap'=>'resource', 'folder'=>'string', 'message'=>'string', 'options='=>'string', 'internal_date='=>'string'], 'new' => ['bool', 'imap'=>'IMAP\Connection', 'folder'=>'string', 'message'=>'string', 'options='=>'string', 'internal_date='=>'string'], diff --git a/tests/Internal/Codebase/InternalCallMapHandlerTest.php b/tests/Internal/Codebase/InternalCallMapHandlerTest.php index 164fb4143d1..2747997492c 100644 --- a/tests/Internal/Codebase/InternalCallMapHandlerTest.php +++ b/tests/Internal/Codebase/InternalCallMapHandlerTest.php @@ -112,7 +112,6 @@ class InternalCallMapHandlerTest extends TestCase 'imagefilter', 'imagegd', 'imagegd2', - 'imageinterlace', 'imageopenpolygon', 'imagepolygon', 'imagerotate', From ffe4375a60be682bd4c59355a815bed5f1d5e089 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Fri, 12 Aug 2022 16:03:56 +0300 Subject: [PATCH 060/178] Clarification of `Reflection::getModifierNames()` result type --- dictionaries/CallMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 329decd3234..42371c395a9 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -11292,7 +11292,7 @@ 'RedisCluster::zScore' => ['float', 'key'=>'string', 'member'=>'string'], 'RedisCluster::zUnionStore' => ['int', 'Output'=>'string', 'ZSetKeys'=>'array', 'Weights='=>'?array', 'aggregateFunction='=>'string'], 'Reflection::export' => ['?string', 'r'=>'reflector', 'return='=>'bool'], -'Reflection::getModifierNames' => ['array', 'modifiers'=>'int'], +'Reflection::getModifierNames' => ['list', 'modifiers'=>'int'], 'ReflectionClass::__clone' => ['void'], 'ReflectionClass::__construct' => ['void', 'argument'=>'object|class-string'], 'ReflectionClass::__toString' => ['string'], From 4498a523b904bc111a5d7ce3240979e484d3cc8f Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Fri, 12 Aug 2022 16:12:36 +0300 Subject: [PATCH 061/178] fix --- dictionaries/CallMap_historical.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 6df5d473acf..f2bb87d83fa 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -5894,7 +5894,7 @@ 'RedisCluster::zScore' => ['float', 'key'=>'string', 'member'=>'string'], 'RedisCluster::zUnionStore' => ['int', 'Output'=>'string', 'ZSetKeys'=>'array', 'Weights='=>'?array', 'aggregateFunction='=>'string'], 'Reflection::export' => ['?string', 'r'=>'reflector', 'return='=>'bool'], - 'Reflection::getModifierNames' => ['array', 'modifiers'=>'int'], + 'Reflection::getModifierNames' => ['list', 'modifiers'=>'int'], 'ReflectionClass::__clone' => ['void'], 'ReflectionClass::__construct' => ['void', 'argument'=>'object|class-string'], 'ReflectionClass::__toString' => ['string'], From 9c67b85f39d92454ec59099b14b535bd2bcd7cab Mon Sep 17 00:00:00 2001 From: Daniel Schmelz Date: Sat, 20 Aug 2022 23:29:03 +0200 Subject: [PATCH 062/178] Fix typos --- docs/annotating_code/supported_annotations.md | 2 +- docs/annotating_code/templated_annotations.md | 4 ++-- docs/annotating_code/type_syntax/scalar_types.md | 2 +- docs/contributing/editing_callmaps.md | 4 ++-- docs/contributing/how_psalm_works.md | 2 +- docs/contributing/philosophy.md | 2 +- docs/manipulating_code/refactoring.md | 2 +- docs/running_psalm/installation.md | 2 +- docs/running_psalm/issues/FalsableReturnStatement.md | 2 +- docs/running_psalm/issues/InvalidExtendClass.md | 2 +- docs/running_psalm/issues/PropertyTypeCoercion.md | 2 +- docs/running_psalm/issues/TaintedCallable.md | 2 +- docs/running_psalm/issues/TaintedLdap.md | 2 +- docs/running_psalm/issues/TaintedTextWithQuotes.md | 2 +- docs/running_psalm/plugins/authoring_plugins.md | 2 +- 15 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/annotating_code/supported_annotations.md b/docs/annotating_code/supported_annotations.md index 0337e9e7d41..62d26541eb4 100644 --- a/docs/annotating_code/supported_annotations.md +++ b/docs/annotating_code/supported_annotations.md @@ -509,7 +509,7 @@ class User { ### `@psalm-require-extends` -The `@psalm-require-extends` annotation allows you to define a requirements that a trait imposes on the using class. +The `@psalm-require-extends` annotation allows you to define the requirements that a trait imposes on the using class. ```php ``` -Passing `');alert('injection');//` as a `GET` param here would would cause the `alert` to trigger. +Passing `');alert('injection');//` as a `GET` param here would cause the `alert` to trigger. ## Mitigations diff --git a/docs/running_psalm/plugins/authoring_plugins.md b/docs/running_psalm/plugins/authoring_plugins.md index 494313970f1..cb683af324e 100644 --- a/docs/running_psalm/plugins/authoring_plugins.md +++ b/docs/running_psalm/plugins/authoring_plugins.md @@ -80,7 +80,7 @@ class SomePlugin implements \Psalm\Plugin\EventHandler\AfterStatementAnalysisInt - `AfterFunctionCallAnalysisInterface` - called after Psalm evaluates a function call to any function defined within the project itself. Can alter the return type or perform modifications of the call. - `AfterFunctionLikeAnalysisInterface` - called after Psalm has completed its analysis of a given function-like. - `AfterMethodCallAnalysisInterface` - called after Psalm analyzes a method call. -- `AfterStatementAnalysisInterface` - called after Psalm evaluates an statement. +- `AfterStatementAnalysisInterface` - called after Psalm evaluates a statement. - `BeforeFileAnalysisInterface` - called before Psalm analyzes a file. - `FunctionExistenceProviderInterface` - can be used to override Psalm's builtin function existence checks for one or more functions. - `FunctionParamsProviderInterface.php` - can be used to override Psalm's builtin function parameter lookup for one or more functions. From 63915d1e2c88f96e0deaeec50720b72943d24507 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 22 Aug 2022 16:44:55 +0200 Subject: [PATCH 063/178] added SensitiveParameter, AllowDynamicProperties php 8.2 attributes --- stubs/CoreGenericClasses.phpstub | 12 ++++++++++++ tests/AttributeTest.php | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/stubs/CoreGenericClasses.phpstub b/stubs/CoreGenericClasses.phpstub index 0dae5ef34d2..168572e7de7 100644 --- a/stubs/CoreGenericClasses.phpstub +++ b/stubs/CoreGenericClasses.phpstub @@ -501,3 +501,15 @@ final class ReturnTypeWillChange { public function __construct() {} } + +#[Attribute] +final class SensitiveParameter +{ + public function __construct() {} +} + +#[Attribute] +final class AllowDynamicProperties +{ + public function __construct() {} +} diff --git a/tests/AttributeTest.php b/tests/AttributeTest.php index efffdbad4de..460bde45b13 100644 --- a/tests/AttributeTest.php +++ b/tests/AttributeTest.php @@ -249,6 +249,32 @@ public function getIterator() [], '8.1' ], + 'allowDynamicProperties' => [ + ' [ + ' [ ' Date: Mon, 22 Aug 2022 16:55:26 +0200 Subject: [PATCH 064/178] imports --- tests/AttributeTest.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/AttributeTest.php b/tests/AttributeTest.php index 460bde45b13..d8c851d0266 100644 --- a/tests/AttributeTest.php +++ b/tests/AttributeTest.php @@ -254,7 +254,9 @@ public function getIterator() namespace AllowDynamicPropertiesAttribute; - #[\AllowDynamicProperties] + use AllowDynamicProperties; + + #[AllowDynamicProperties] class Foo {}', [], @@ -266,9 +268,11 @@ class Foo namespace SensitiveParameter; + use SensitiveParameter; + class HelloWorld { public function __construct( - #[\SensitiveParameter] string $password + #[SensitiveParameter] string $password ) {} }', [], From 8407cacc023c6471c91703655c0f5c321e8b2c82 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 22 Aug 2022 16:55:32 +0200 Subject: [PATCH 065/178] improve CI error message --- src/Psalm/Internal/CliUtils.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Psalm/Internal/CliUtils.php b/src/Psalm/Internal/CliUtils.php index f8c88734001..abe54ff20db 100644 --- a/src/Psalm/Internal/CliUtils.php +++ b/src/Psalm/Internal/CliUtils.php @@ -636,7 +636,7 @@ public static function initPhpVersion(array $options, Config $config, ProjectAna if (isset($options['php-version'])) { if (!is_string($options['php-version'])) { - die('Expecting a version number in the format x.y' . PHP_EOL); + die('Expecting a version number in the format x.y, got'. get_debug_type($options['php-version']) . PHP_EOL); } $version = $options['php-version']; $source = 'cli'; From 198347fac7626b269bc45c3249911c661e933ed8 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 22 Aug 2022 16:58:23 +0200 Subject: [PATCH 066/178] fix test --- tests/AttributeTest.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/AttributeTest.php b/tests/AttributeTest.php index d8c851d0266..b77b9d6aa67 100644 --- a/tests/AttributeTest.php +++ b/tests/AttributeTest.php @@ -258,10 +258,8 @@ public function getIterator() #[AllowDynamicProperties] class Foo - {}', - [], - [], - '8.2' + {} + ', ], 'sensitiveParameter' => [ ' [ ' Date: Mon, 22 Aug 2022 16:59:27 +0200 Subject: [PATCH 067/178] Restore CliUtils.php --- src/Psalm/Internal/CliUtils.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Psalm/Internal/CliUtils.php b/src/Psalm/Internal/CliUtils.php index abe54ff20db..f8c88734001 100644 --- a/src/Psalm/Internal/CliUtils.php +++ b/src/Psalm/Internal/CliUtils.php @@ -636,7 +636,7 @@ public static function initPhpVersion(array $options, Config $config, ProjectAna if (isset($options['php-version'])) { if (!is_string($options['php-version'])) { - die('Expecting a version number in the format x.y, got'. get_debug_type($options['php-version']) . PHP_EOL); + die('Expecting a version number in the format x.y' . PHP_EOL); } $version = $options['php-version']; $source = 'cli'; From 93a293c673431478461144f39f881a0eadc6e419 Mon Sep 17 00:00:00 2001 From: Denis Kuznetsov Date: Tue, 23 Aug 2022 03:24:32 +0300 Subject: [PATCH 068/178] Allow any attribute for complex types in schema --- config.xsd | 50 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/config.xsd b/config.xsd index e942fcbaaf3..c9a4cd88895 100644 --- a/config.xsd +++ b/config.xsd @@ -130,6 +130,7 @@ + @@ -138,24 +139,25 @@ - + - + - + + @@ -165,14 +167,14 @@ - + - + @@ -185,21 +187,21 @@ - + - + - + @@ -207,21 +209,21 @@ - + - + - + @@ -229,6 +231,7 @@ + @@ -237,10 +240,11 @@ + - + @@ -527,7 +531,7 @@ - + @@ -540,11 +544,13 @@ + + @@ -557,12 +563,14 @@ + + @@ -576,11 +584,13 @@ + + @@ -594,11 +604,13 @@ + + @@ -612,11 +624,13 @@ + + @@ -630,11 +644,13 @@ + + @@ -648,11 +664,13 @@ + + @@ -666,28 +684,32 @@ + + - + + + From 4b1adaafecc98f0233f36d2b7a0ab3db7be431fc Mon Sep 17 00:00:00 2001 From: Thomas Gerbet Date: Tue, 23 Aug 2022 14:01:44 +0200 Subject: [PATCH 069/178] Allow *bin2hex and *bin2base64 functions to keep non-empty-string type Those functions should not return a string when they receive a non-empty-string in input. The following example is expected to work: ```php return + * @template T as string + * @param T $string + * @return (T is non-empty-string ? non-empty-string : string) */ function base64_encode(string $string) : string {} +/** + * @psalm-pure + * + * @template T as string + * @param T $string + * @return (T is non-empty-string ? non-empty-string : string) + */ +function bin2hex(string $string): string {} + /** * @psalm-pure * From 6bc714c867b27f5138d3b8224a3524d03ed61818 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 24 Aug 2022 16:04:22 +0200 Subject: [PATCH 070/178] Add support for callable in array_reduce --- .../ArrayReduceReturnTypeProvider.php | 2 +- tests/ClosureTest.php | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReduceReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReduceReturnTypeProvider.php index a9d27ba8774..4632eaecefa 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReduceReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReduceReturnTypeProvider.php @@ -100,7 +100,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $initial_type = $reduce_return_type; - if ($closure_types = $function_call_arg_type->getClosureTypes()) { + if ($closure_types = $function_call_arg_type->getClosureTypes() ?: $function_call_arg_type->getCallableTypes()) { $closure_atomic_type = reset($closure_types); $closure_return_type = $closure_atomic_type->return_type ?: Type::getMixed(); diff --git a/tests/ClosureTest.php b/tests/ClosureTest.php index 53e5cc3f9b2..cbd3b3360c4 100644 --- a/tests/ClosureTest.php +++ b/tests/ClosureTest.php @@ -565,6 +565,22 @@ function maker(string $className) { '$result' => 'array{stdClass}' ], ], + 'CallableWithArrayReduce' => [ + ' [ + '$result' => 'int' + ], + ], 'FirstClassCallable:NamedFunction:is_int' => [ ' Date: Wed, 24 Aug 2022 21:31:02 +0200 Subject: [PATCH 071/178] Configure a correct attribute target in stubs/CoreGenericClasses.phpstub --- stubs/CoreGenericClasses.phpstub | 6 +++--- tests/AttributeTest.php | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/stubs/CoreGenericClasses.phpstub b/stubs/CoreGenericClasses.phpstub index 168572e7de7..f988fcf26c6 100644 --- a/stubs/CoreGenericClasses.phpstub +++ b/stubs/CoreGenericClasses.phpstub @@ -496,19 +496,19 @@ final class WeakMap implements ArrayAccess, Countable, IteratorAggregate, Traver } -#[Attribute] +#[Attribute(Attribute::TARGET_METHOD)] final class ReturnTypeWillChange { public function __construct() {} } -#[Attribute] +#[Attribute(Attribute::TARGET_PARAMETER)] final class SensitiveParameter { public function __construct() {} } -#[Attribute] +#[Attribute(Attribute::TARGET_CLASS)] final class AllowDynamicProperties { public function __construct() {} diff --git a/tests/AttributeTest.php b/tests/AttributeTest.php index b77b9d6aa67..76d511fe853 100644 --- a/tests/AttributeTest.php +++ b/tests/AttributeTest.php @@ -787,6 +787,22 @@ public function anotherMethod(): string false, '8.1', ], + 'sensitiveParameterOnMethod' => [ + ' 'Attribute SensitiveParameter cannot be used on a method', + ], ]; } } From 6a6922d29ef32597e4faa8dac56c22b6efbb9efe Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Wed, 24 Aug 2022 14:19:57 +0200 Subject: [PATCH 072/178] Update call maps for MongoDB extension --- dictionaries/CallMap.php | 461 ++++++++++++++++------------ dictionaries/CallMap_historical.php | 461 ++++++++++++++++------------ 2 files changed, 524 insertions(+), 398 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 3590fe42c7f..1f6ca718260 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -7693,256 +7693,319 @@ 'MongoDB::setReadPreference' => ['bool', 'read_preference'=>'string', 'tags='=>'array'], 'MongoDB::setSlaveOkay' => ['bool', 'ok='=>'bool'], 'MongoDB::setWriteConcern' => ['bool', 'w'=>'mixed', 'wtimeout='=>'int'], -'MongoDB\BSON\Binary::__construct' => ['void', 'data'=>'string', 'type'=>'int'], -'MongoDB\BSON\Binary::__toString' => ['string'], +'MongoDB\BSON\Binary::__construct' => ['void', 'data' => 'string', 'type' => 'int'], 'MongoDB\BSON\Binary::getData' => ['string'], 'MongoDB\BSON\Binary::getType' => ['int'], -'MongoDB\BSON\binary::jsonSerialize' => ['mixed'], -'MongoDB\BSON\binary::serialize' => ['string'], -'MongoDB\BSON\binary::unserialize' => ['void', 'serialized'=>'string'], -'MongoDB\BSON\binaryinterface::__toString' => ['string'], -'MongoDB\BSON\binaryinterface::getData' => ['string'], -'MongoDB\BSON\binaryinterface::getType' => ['int'], -'MongoDB\BSON\dbpointer::__construct' => ['void'], -'MongoDB\BSON\dbpointer::__toString' => ['string'], -'MongoDB\BSON\dbpointer::jsonSerialize' => ['mixed'], -'MongoDB\BSON\dbpointer::serialize' => ['string'], -'MongoDB\BSON\dbpointer::unserialize' => ['void', 'serialized'=>'string'], -'MongoDB\BSON\Decimal128::__construct' => ['void', 'value='=>'string'], +'MongoDB\BSON\Binary::__toString' => ['string'], +'MongoDB\BSON\Binary::serialize' => ['string'], +'MongoDB\BSON\Binary::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\Binary::jsonSerialize' => ['mixed'], +'MongoDB\BSON\BinaryInterface::getData' => ['void'], +'MongoDB\BSON\BinaryInterface::getType' => ['void'], +'MongoDB\BSON\BinaryInterface::__toString' => ['string'], +'MongoDB\BSON\DBPointer::__toString' => ['string'], +'MongoDB\BSON\DBPointer::serialize' => ['string'], +'MongoDB\BSON\DBPointer::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\DBPointer::jsonSerialize' => ['mixed'], +'MongoDB\BSON\Decimal128::__construct' => ['void', 'value' => 'string'], 'MongoDB\BSON\Decimal128::__toString' => ['string'], -'MongoDB\BSON\decimal128::jsonSerialize' => ['mixed'], -'MongoDB\BSON\decimal128::serialize' => ['string'], -'MongoDB\BSON\decimal128::unserialize' => ['void', 'serialized'=>'string'], -'MongoDB\BSON\decimal128interface::__toString' => ['string'], -'MongoDB\BSON\fromJSON' => ['string', 'json'=>'string'], -'MongoDB\BSON\fromPHP' => ['string', 'value'=>'array|object'], -'MongoDB\BSON\int64::__construct' => ['void'], -'MongoDB\BSON\int64::__toString' => ['string'], -'MongoDB\BSON\int64::jsonSerialize' => ['mixed'], -'MongoDB\BSON\int64::serialize' => ['string'], -'MongoDB\BSON\int64::unserialize' => ['void', 'serialized'=>'string'], -'MongoDB\BSON\Javascript::__construct' => ['void', 'code'=>'string', 'scope='=>'array|object'], -'MongoDB\BSON\javascript::__toString' => ['string'], -'MongoDB\BSON\javascript::getCode' => ['string'], -'MongoDB\BSON\javascript::getScope' => ['?object'], -'MongoDB\BSON\javascript::jsonSerialize' => ['mixed'], -'MongoDB\BSON\javascript::serialize' => ['string'], -'MongoDB\BSON\javascript::unserialize' => ['void', 'serialized'=>'string'], -'MongoDB\BSON\javascriptinterface::__toString' => ['string'], -'MongoDB\BSON\javascriptinterface::getCode' => ['string'], -'MongoDB\BSON\javascriptinterface::getScope' => ['?object'], -'MongoDB\BSON\maxkey::__construct' => ['void'], -'MongoDB\BSON\maxkey::jsonSerialize' => ['mixed'], -'MongoDB\BSON\maxkey::serialize' => ['string'], -'MongoDB\BSON\maxkey::unserialize' => ['void', 'serialized'=>'string'], -'MongoDB\BSON\minkey::__construct' => ['void'], -'MongoDB\BSON\minkey::jsonSerialize' => ['mixed'], -'MongoDB\BSON\minkey::serialize' => ['string'], -'MongoDB\BSON\minkey::unserialize' => ['void', 'serialized'=>'string'], -'MongoDB\BSON\ObjectId::__construct' => ['void', 'id='=>'string'], +'MongoDB\BSON\Decimal128::serialize' => ['string'], +'MongoDB\BSON\Decimal128::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\Decimal128::jsonSerialize' => ['mixed'], +'MongoDB\BSON\Decimal128Interface::__toString' => ['void'], +'MongoDB\BSON\Int64::__toString' => ['string'], +'MongoDB\BSON\Int64::serialize' => ['string'], +'MongoDB\BSON\Int64::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\Int64::jsonSerialize' => ['mixed'], +'MongoDB\BSON\Javascript::__construct' => ['void', 'code' => 'string', 'scope=' => 'object|array|null'], +'MongoDB\BSON\Javascript::getCode' => ['string'], +'MongoDB\BSON\Javascript::getScope' => ['?object'], +'MongoDB\BSON\Javascript::__toString' => ['string'], +'MongoDB\BSON\Javascript::serialize' => ['string'], +'MongoDB\BSON\Javascript::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\Javascript::jsonSerialize' => ['mixed'], +'MongoDB\BSON\JavascriptInterface::getCode' => ['void'], +'MongoDB\BSON\JavascriptInterface::getScope' => ['void'], +'MongoDB\BSON\JavascriptInterface::__toString' => ['void'], +'MongoDB\BSON\MaxKey::serialize' => ['string'], +'MongoDB\BSON\MaxKey::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\MaxKey::jsonSerialize' => ['mixed'], +'MongoDB\BSON\MinKey::serialize' => ['string'], +'MongoDB\BSON\MinKey::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\MinKey::jsonSerialize' => ['mixed'], +'MongoDB\BSON\ObjectId::__construct' => ['void', 'id=' => '?string'], +'MongoDB\BSON\ObjectId::getTimestamp' => ['int'], 'MongoDB\BSON\ObjectId::__toString' => ['string'], -'MongoDB\BSON\objectid::getTimestamp' => ['int'], -'MongoDB\BSON\objectid::jsonSerialize' => ['mixed'], -'MongoDB\BSON\objectid::serialize' => ['string'], -'MongoDB\BSON\objectid::unserialize' => ['void', 'serialized'=>'string'], -'MongoDB\BSON\objectidinterface::__toString' => ['string'], -'MongoDB\BSON\objectidinterface::getTimestamp' => ['int'], -'MongoDB\BSON\Regex::__construct' => ['void', 'pattern'=>'string', 'flags='=>'string'], -'MongoDB\BSON\Regex::__toString' => ['string'], -'MongoDB\BSON\Regex::getFlags' => ['string'], +'MongoDB\BSON\ObjectId::serialize' => ['string'], +'MongoDB\BSON\ObjectId::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\ObjectId::jsonSerialize' => ['mixed'], +'MongoDB\BSON\ObjectIdInterface::getTimestamp' => ['void'], +'MongoDB\BSON\ObjectIdInterface::__toString' => ['void'], +'MongoDB\BSON\Regex::__construct' => ['void', 'pattern' => 'string', 'flags=' => 'string'], 'MongoDB\BSON\Regex::getPattern' => ['string'], -'MongoDB\BSON\regex::jsonSerialize' => ['mixed'], -'MongoDB\BSON\regex::serialize' => ['string'], -'MongoDB\BSON\regex::unserialize' => ['void', 'serialized'=>'string'], -'MongoDB\BSON\regexinterface::__toString' => ['string'], -'MongoDB\BSON\regexinterface::getFlags' => ['string'], -'MongoDB\BSON\regexinterface::getPattern' => ['string'], -'MongoDB\BSON\Serializable::bsonSerialize' => ['array|object'], -'MongoDB\BSON\symbol::__construct' => ['void'], -'MongoDB\BSON\symbol::__toString' => ['string'], -'MongoDB\BSON\symbol::jsonSerialize' => ['mixed'], -'MongoDB\BSON\symbol::serialize' => ['string'], -'MongoDB\BSON\symbol::unserialize' => ['void', 'serialized'=>'string'], -'MongoDB\BSON\Timestamp::__construct' => ['void', 'increment'=>'int', 'timestamp'=>'int'], +'MongoDB\BSON\Regex::getFlags' => ['string'], +'MongoDB\BSON\Regex::__toString' => ['string'], +'MongoDB\BSON\Regex::serialize' => ['string'], +'MongoDB\BSON\Regex::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\Regex::jsonSerialize' => ['mixed'], +'MongoDB\BSON\RegexInterface::getPattern' => ['void'], +'MongoDB\BSON\RegexInterface::getFlags' => ['void'], +'MongoDB\BSON\RegexInterface::__toString' => ['void'], +'MongoDB\BSON\Serializable::bsonSerialize' => ['void'], +'MongoDB\BSON\Symbol::__toString' => ['string'], +'MongoDB\BSON\Symbol::serialize' => ['string'], +'MongoDB\BSON\Symbol::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\Symbol::jsonSerialize' => ['mixed'], +'MongoDB\BSON\Timestamp::__construct' => ['void', 'increment' => 'string|int', 'timestamp' => 'string|int'], +'MongoDB\BSON\Timestamp::getTimestamp' => ['int'], +'MongoDB\BSON\Timestamp::getIncrement' => ['int'], 'MongoDB\BSON\Timestamp::__toString' => ['string'], -'MongoDB\BSON\timestamp::getIncrement' => ['int'], -'MongoDB\BSON\timestamp::getTimestamp' => ['int'], -'MongoDB\BSON\timestamp::jsonSerialize' => ['mixed'], -'MongoDB\BSON\timestamp::serialize' => ['string'], -'MongoDB\BSON\timestamp::unserialize' => ['void', 'serialized'=>'string'], -'MongoDB\BSON\timestampinterface::__toString' => ['string'], -'MongoDB\BSON\timestampinterface::getIncrement' => ['int'], -'MongoDB\BSON\timestampinterface::getTimestamp' => ['int'], -'MongoDB\BSON\toJSON' => ['string', 'bson'=>'string'], -'MongoDB\BSON\toPHP' => ['object', 'bson'=>'string', 'typeMap='=>'array'], -'MongoDB\BSON\undefined::__construct' => ['void'], -'MongoDB\BSON\undefined::__toString' => ['string'], -'MongoDB\BSON\undefined::jsonSerialize' => ['mixed'], -'MongoDB\BSON\undefined::serialize' => ['string'], -'MongoDB\BSON\undefined::unserialize' => ['void', 'serialized'=>'string'], -'MongoDB\BSON\Unserializable::bsonUnserialize' => ['void', 'data'=>'array'], -'MongoDB\BSON\UTCDateTime::__construct' => ['void', 'milliseconds='=>'int|DateTimeInterface'], -'MongoDB\BSON\UTCDateTime::__toString' => ['string'], -'MongoDB\BSON\utcdatetime::jsonSerialize' => ['mixed'], -'MongoDB\BSON\utcdatetime::serialize' => ['string'], +'MongoDB\BSON\Timestamp::serialize' => ['string'], +'MongoDB\BSON\Timestamp::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\Timestamp::jsonSerialize' => ['mixed'], +'MongoDB\BSON\TimestampInterface::getTimestamp' => ['void'], +'MongoDB\BSON\TimestampInterface::getIncrement' => ['void'], +'MongoDB\BSON\TimestampInterface::__toString' => ['void'], +'MongoDB\BSON\UTCDateTime::__construct' => ['void', 'milliseconds=' => 'DateTimeInterface|string|int|float|null'], 'MongoDB\BSON\UTCDateTime::toDateTime' => ['DateTime'], -'MongoDB\BSON\utcdatetime::unserialize' => ['void', 'serialized'=>'string'], -'MongoDB\BSON\utcdatetimeinterface::__toString' => ['string'], -'MongoDB\BSON\utcdatetimeinterface::toDateTime' => ['DateTime'], -'MongoDB\Driver\BulkWrite::__construct' => ['void', 'ordered='=>'bool'], +'MongoDB\BSON\UTCDateTime::__toString' => ['string'], +'MongoDB\BSON\UTCDateTime::serialize' => ['string'], +'MongoDB\BSON\UTCDateTime::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\UTCDateTime::jsonSerialize' => ['mixed'], +'MongoDB\BSON\UTCDateTimeInterface::toDateTime' => ['void'], +'MongoDB\BSON\UTCDateTimeInterface::__toString' => ['void'], +'MongoDB\BSON\Undefined::__toString' => ['string'], +'MongoDB\BSON\Undefined::serialize' => ['string'], +'MongoDB\BSON\Undefined::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\Undefined::jsonSerialize' => ['mixed'], +'MongoDB\BSON\Unserializable::bsonUnserialize' => ['void', 'data' => 'array'], +'MongoDB\Driver\BulkWrite::__construct' => ['void', 'options=' => '?array'], 'MongoDB\Driver\BulkWrite::count' => ['int'], -'MongoDB\Driver\BulkWrite::delete' => ['void', 'filter'=>'array|object', 'deleteOptions='=>'array'], -'MongoDB\Driver\BulkWrite::insert' => ['void|MongoDB\BSON\ObjectId', 'document'=>'array|object'], -'MongoDB\Driver\BulkWrite::update' => ['void', 'filter'=>'array|object', 'newObj'=>'array|object', 'updateOptions='=>'array'], -'MongoDB\Driver\Command::__construct' => ['void', 'document'=>'array|object'], -'MongoDB\Driver\Cursor::__construct' => ['void', 'server'=>'Server', 'responseDocument'=>'string'], +'MongoDB\Driver\BulkWrite::delete' => ['void', 'filter' => 'object|array', 'deleteOptions=' => '?array'], +'MongoDB\Driver\BulkWrite::insert' => ['mixed', 'document' => 'object|array'], +'MongoDB\Driver\BulkWrite::update' => ['void', 'filter' => 'object|array', 'newObj' => 'object|array', 'updateOptions=' => '?array'], +'MongoDB\Driver\ClientEncryption::__construct' => ['void', 'options' => 'array'], +'MongoDB\Driver\ClientEncryption::addKeyAltName' => ['?object', 'keyId' => 'MongoDB\BSON\Binary', 'keyAltName' => 'string'], +'MongoDB\Driver\ClientEncryption::createDataKey' => ['MongoDB\BSON\Binary', 'kmsProvider' => 'string', 'options=' => '?array'], +'MongoDB\Driver\ClientEncryption::decrypt' => ['mixed', 'value' => 'MongoDB\BSON\Binary'], +'MongoDB\Driver\ClientEncryption::deleteKey' => ['object', 'keyId' => 'MongoDB\BSON\Binary'], +'MongoDB\Driver\ClientEncryption::encrypt' => ['MongoDB\BSON\Binary', 'value' => 'mixed', 'options=' => '?array'], +'MongoDB\Driver\ClientEncryption::getKey' => ['?object', 'keyId' => 'MongoDB\BSON\Binary'], +'MongoDB\Driver\ClientEncryption::getKeyByAltName' => ['?object', 'keyAltName' => 'string'], +'MongoDB\Driver\ClientEncryption::getKeys' => ['MongoDB\Driver\Cursor'], +'MongoDB\Driver\ClientEncryption::removeKeyAltName' => ['?object', 'keyId' => 'MongoDB\BSON\Binary', 'keyAltName' => 'string'], +'MongoDB\Driver\ClientEncryption::rewrapManyDataKey' => ['object', 'filter' => 'object|array', 'options=' => '?array'], +'MongoDB\Driver\Command::__construct' => ['void', 'document' => 'object|array', 'commandOptions=' => '?array'], +'MongoDB\Driver\Cursor::current' => ['object|array|null'], 'MongoDB\Driver\Cursor::getId' => ['MongoDB\Driver\CursorId'], 'MongoDB\Driver\Cursor::getServer' => ['MongoDB\Driver\Server'], 'MongoDB\Driver\Cursor::isDead' => ['bool'], -'MongoDB\Driver\Cursor::setTypeMap' => ['void', 'typemap'=>'array'], +'MongoDB\Driver\Cursor::key' => ['?int'], +'MongoDB\Driver\Cursor::next' => ['void'], +'MongoDB\Driver\Cursor::rewind' => ['void'], +'MongoDB\Driver\Cursor::setTypeMap' => ['void', 'typemap' => 'array'], 'MongoDB\Driver\Cursor::toArray' => ['array'], -'MongoDB\Driver\CursorId::__construct' => ['void', 'id'=>'string'], +'MongoDB\Driver\Cursor::valid' => ['bool'], 'MongoDB\Driver\CursorId::__toString' => ['string'], 'MongoDB\Driver\CursorId::serialize' => ['string'], -'MongoDB\Driver\CursorId::unserialize' => ['void', 'serialized'=>'string'], -'mongodb\driver\exception\commandexception::getResultDocument' => ['object'], -'MongoDB\Driver\Exception\RuntimeException::__clone' => ['void'], -'MongoDB\Driver\Exception\RuntimeException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?RuntimeException|?Throwable'], +'MongoDB\Driver\CursorId::unserialize' => ['void', 'serialized' => ''], +'MongoDB\Driver\CursorInterface::current' => ['object|array|null'], +'MongoDB\Driver\CursorInterface::getId' => ['void'], +'MongoDB\Driver\CursorInterface::getServer' => ['void'], +'MongoDB\Driver\CursorInterface::isDead' => ['void'], +'MongoDB\Driver\CursorInterface::key' => ['?int'], +'MongoDB\Driver\CursorInterface::next' => ['void'], +'MongoDB\Driver\CursorInterface::rewind' => ['void'], +'MongoDB\Driver\CursorInterface::setTypeMap' => ['void', 'typemap' => 'array'], +'MongoDB\Driver\CursorInterface::toArray' => ['void'], +'MongoDB\Driver\CursorInterface::valid' => ['bool'], +'MongoDB\Driver\Exception\AuthenticationException::__toString' => ['string'], +'MongoDB\Driver\Exception\BulkWriteException::__toString' => ['string'], +'MongoDB\Driver\Exception\CommandException::getResultDocument' => ['object'], +'MongoDB\Driver\Exception\CommandException::__toString' => ['string'], +'MongoDB\Driver\Exception\ConnectionException::__toString' => ['string'], +'MongoDB\Driver\Exception\ConnectionTimeoutException::__toString' => ['string'], +'MongoDB\Driver\Exception\EncryptionException::__toString' => ['string'], +'MongoDB\Driver\Exception\Exception::__toString' => ['string'], +'MongoDB\Driver\Exception\ExecutionTimeoutException::__toString' => ['string'], +'MongoDB\Driver\Exception\InvalidArgumentException::__toString' => ['string'], +'MongoDB\Driver\Exception\LogicException::__toString' => ['string'], +'MongoDB\Driver\Exception\RuntimeException::hasErrorLabel' => ['bool', 'errorLabel' => 'string'], 'MongoDB\Driver\Exception\RuntimeException::__toString' => ['string'], -'MongoDB\Driver\Exception\RuntimeException::__wakeup' => ['void'], -'MongoDB\Driver\Exception\RuntimeException::getCode' => ['int'], -'MongoDB\Driver\Exception\RuntimeException::getFile' => ['string'], -'MongoDB\Driver\Exception\RuntimeException::getLine' => ['int'], -'MongoDB\Driver\Exception\RuntimeException::getMessage' => ['string'], -'MongoDB\Driver\Exception\RuntimeException::getPrevious' => ['RuntimeException|Throwable'], -'MongoDB\Driver\Exception\RuntimeException::getTrace' => ['list\',args?:array}>'], -'MongoDB\Driver\Exception\RuntimeException::getTraceAsString' => ['string'], -'mongodb\driver\exception\runtimeexception::hasErrorLabel' => ['bool', 'errorLabel'=>'string'], -'MongoDB\Driver\Exception\WriteException::__clone' => ['void'], -'MongoDB\Driver\Exception\WriteException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?RuntimeException|?Throwable'], -'MongoDB\Driver\Exception\WriteException::__toString' => ['string'], -'MongoDB\Driver\Exception\WriteException::__wakeup' => ['void'], -'MongoDB\Driver\Exception\WriteException::getCode' => ['int'], -'MongoDB\Driver\Exception\WriteException::getFile' => ['string'], -'MongoDB\Driver\Exception\WriteException::getLine' => ['int'], -'MongoDB\Driver\Exception\WriteException::getMessage' => ['string'], -'MongoDB\Driver\Exception\WriteException::getPrevious' => ['RuntimeException|Throwable'], -'MongoDB\Driver\Exception\WriteException::getTrace' => ['list\',args?:array}>'], -'MongoDB\Driver\Exception\WriteException::getTraceAsString' => ['string'], +'MongoDB\Driver\Exception\SSLConnectionException::__toString' => ['string'], +'MongoDB\Driver\Exception\ServerException::__toString' => ['string'], +'MongoDB\Driver\Exception\UnexpectedValueException::__toString' => ['string'], 'MongoDB\Driver\Exception\WriteException::getWriteResult' => ['MongoDB\Driver\WriteResult'], -'MongoDB\Driver\Manager::__construct' => ['void', 'uri'=>'string', 'options='=>'array', 'driverOptions='=>'array'], -'MongoDB\Driver\Manager::executeBulkWrite' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'bulk'=>'MongoDB\Driver\BulkWrite', 'writeConcern='=>'MongoDB\Driver\WriteConcern'], -'MongoDB\Driver\Manager::executeCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'readPreference='=>'MongoDB\Driver\ReadPreference'], -'MongoDB\Driver\Manager::executeDelete' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'filter'=>'array|object', 'deleteOptions='=>'array', 'writeConcern='=>'MongoDB\Driver\WriteConcern'], -'MongoDB\Driver\Manager::executeInsert' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'document'=>'array|object', 'writeConcern='=>'MongoDB\Driver\WriteConcern'], -'MongoDB\Driver\Manager::executeQuery' => ['MongoDB\Driver\Cursor', 'namespace'=>'string', 'query'=>'MongoDB\Driver\Query', 'readPreference='=>'MongoDB\Driver\ReadPreference'], -'mongodb\driver\manager::executeReadCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'array'], -'mongodb\driver\manager::executeReadWriteCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'array'], -'MongoDB\Driver\Manager::executeUpdate' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'filter'=>'array|object', 'newObj'=>'array|object', 'updateOptions='=>'array', 'writeConcern='=>'MongoDB\Driver\WriteConcern'], -'mongodb\driver\manager::executeWriteCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'array'], +'MongoDB\Driver\Exception\WriteException::__toString' => ['string'], +'MongoDB\Driver\Manager::__construct' => ['void', 'uri=' => '?string', 'uriOptions=' => '?array', 'driverOptions=' => '?array'], +'MongoDB\Driver\Manager::addSubscriber' => ['void', 'subscriber' => 'MongoDB\Driver\Monitoring\Subscriber'], +'MongoDB\Driver\Manager::createClientEncryption' => ['MongoDB\Driver\ClientEncryption', 'options' => 'array'], +'MongoDB\Driver\Manager::executeBulkWrite' => ['MongoDB\Driver\WriteResult', 'namespace' => 'string', 'bulk' => 'MongoDB\Driver\BulkWrite', 'options=' => 'MongoDB\Driver\WriteConcern|array|null'], +'MongoDB\Driver\Manager::executeCommand' => ['MongoDB\Driver\Cursor', 'db' => 'string', 'command' => 'MongoDB\Driver\Command', 'options=' => 'MongoDB\Driver\ReadPreference|array|null'], +'MongoDB\Driver\Manager::executeQuery' => ['MongoDB\Driver\Cursor', 'namespace' => 'string', 'query' => 'MongoDB\Driver\Query', 'options=' => 'MongoDB\Driver\ReadPreference|array|null'], +'MongoDB\Driver\Manager::executeReadCommand' => ['MongoDB\Driver\Cursor', 'db' => 'string', 'command' => 'MongoDB\Driver\Command', 'options=' => '?array'], +'MongoDB\Driver\Manager::executeReadWriteCommand' => ['MongoDB\Driver\Cursor', 'db' => 'string', 'command' => 'MongoDB\Driver\Command', 'options=' => '?array'], +'MongoDB\Driver\Manager::executeWriteCommand' => ['MongoDB\Driver\Cursor', 'db' => 'string', 'command' => 'MongoDB\Driver\Command', 'options=' => '?array'], +'MongoDB\Driver\Manager::getEncryptedFieldsMap' => ['object|array|null'], 'MongoDB\Driver\Manager::getReadConcern' => ['MongoDB\Driver\ReadConcern'], 'MongoDB\Driver\Manager::getReadPreference' => ['MongoDB\Driver\ReadPreference'], -'MongoDB\Driver\Manager::getServers' => ['MongoDB\Driver\Server[]'], +'MongoDB\Driver\Manager::getServers' => ['array'], 'MongoDB\Driver\Manager::getWriteConcern' => ['MongoDB\Driver\WriteConcern'], -'MongoDB\Driver\Manager::selectServer' => ['MongoDB\Driver\Server', 'readPreference'=>'MongoDB\Driver\ReadPreference'], -'mongodb\driver\manager::startSession' => ['MongoDB\Driver\Session', 'options='=>'array'], -'mongodb\driver\monitoring\commandfailedevent::getCommandName' => ['string'], -'mongodb\driver\monitoring\commandfailedevent::getDurationMicros' => ['int'], -'mongodb\driver\monitoring\commandfailedevent::getError' => ['Exception'], -'mongodb\driver\monitoring\commandfailedevent::getOperationId' => ['string'], -'mongodb\driver\monitoring\commandfailedevent::getReply' => ['object'], -'mongodb\driver\monitoring\commandfailedevent::getRequestId' => ['string'], -'mongodb\driver\monitoring\commandfailedevent::getServer' => ['MongoDB\Driver\Server'], -'mongodb\driver\monitoring\commandstartedevent::getCommand' => ['object'], -'mongodb\driver\monitoring\commandstartedevent::getCommandName' => ['string'], -'mongodb\driver\monitoring\commandstartedevent::getDatabaseName' => ['string'], -'mongodb\driver\monitoring\commandstartedevent::getOperationId' => ['string'], -'mongodb\driver\monitoring\commandstartedevent::getRequestId' => ['string'], -'mongodb\driver\monitoring\commandstartedevent::getServer' => ['MongoDB\Driver\Server'], -'mongodb\driver\monitoring\commandsubscriber::commandFailed' => ['void', 'event'=>'MongoDB\Driver\Monitoring\CommandFailedEvent'], -'mongodb\driver\monitoring\commandsubscriber::commandStarted' => ['void', 'event'=>'MongoDB\Driver\Monitoring\CommandStartedEvent'], -'mongodb\driver\monitoring\commandsubscriber::commandSucceeded' => ['void', 'event'=>'MongoDB\Driver\Monitoring\CommandSucceededEvent'], -'mongodb\driver\monitoring\commandsucceededevent::getCommandName' => ['string'], -'mongodb\driver\monitoring\commandsucceededevent::getDurationMicros' => ['int'], -'mongodb\driver\monitoring\commandsucceededevent::getOperationId' => ['string'], -'mongodb\driver\monitoring\commandsucceededevent::getReply' => ['object'], -'mongodb\driver\monitoring\commandsucceededevent::getRequestId' => ['string'], -'mongodb\driver\monitoring\commandsucceededevent::getServer' => ['MongoDB\Driver\Server'], -'MongoDB\Driver\Query::__construct' => ['void', 'filter'=>'array|object', 'queryOptions='=>'array'], -'MongoDB\Driver\ReadConcern::__construct' => ['void', 'level='=>'string'], -'MongoDB\Driver\ReadConcern::bsonSerialize' => ['object'], +'MongoDB\Driver\Manager::removeSubscriber' => ['void', 'subscriber' => 'MongoDB\Driver\Monitoring\Subscriber'], +'MongoDB\Driver\Manager::selectServer' => ['MongoDB\Driver\Server', 'readPreference=' => '?MongoDB\Driver\ReadPreference'], +'MongoDB\Driver\Manager::startSession' => ['MongoDB\Driver\Session', 'options=' => '?array'], +'MongoDB\Driver\Monitoring\CommandFailedEvent::getCommandName' => ['string'], +'MongoDB\Driver\Monitoring\CommandFailedEvent::getDurationMicros' => ['int'], +'MongoDB\Driver\Monitoring\CommandFailedEvent::getError' => ['Exception'], +'MongoDB\Driver\Monitoring\CommandFailedEvent::getOperationId' => ['string'], +'MongoDB\Driver\Monitoring\CommandFailedEvent::getReply' => ['object'], +'MongoDB\Driver\Monitoring\CommandFailedEvent::getRequestId' => ['string'], +'MongoDB\Driver\Monitoring\CommandFailedEvent::getServer' => ['MongoDB\Driver\Server'], +'MongoDB\Driver\Monitoring\CommandFailedEvent::getServiceId' => ['?MongoDB\BSON\ObjectId'], +'MongoDB\Driver\Monitoring\CommandFailedEvent::getServerConnectionId' => ['?int'], +'MongoDB\Driver\Monitoring\CommandStartedEvent::getCommand' => ['object'], +'MongoDB\Driver\Monitoring\CommandStartedEvent::getCommandName' => ['string'], +'MongoDB\Driver\Monitoring\CommandStartedEvent::getDatabaseName' => ['string'], +'MongoDB\Driver\Monitoring\CommandStartedEvent::getOperationId' => ['string'], +'MongoDB\Driver\Monitoring\CommandStartedEvent::getRequestId' => ['string'], +'MongoDB\Driver\Monitoring\CommandStartedEvent::getServer' => ['MongoDB\Driver\Server'], +'MongoDB\Driver\Monitoring\CommandStartedEvent::getServiceId' => ['?MongoDB\BSON\ObjectId'], +'MongoDB\Driver\Monitoring\CommandStartedEvent::getServerConnectionId' => ['?int'], +'MongoDB\Driver\Monitoring\CommandSubscriber::commandStarted' => ['void', 'event' => 'MongoDB\Driver\Monitoring\CommandStartedEvent'], +'MongoDB\Driver\Monitoring\CommandSubscriber::commandSucceeded' => ['void', 'event' => 'MongoDB\Driver\Monitoring\CommandSucceededEvent'], +'MongoDB\Driver\Monitoring\CommandSubscriber::commandFailed' => ['void', 'event' => 'MongoDB\Driver\Monitoring\CommandFailedEvent'], +'MongoDB\Driver\Monitoring\CommandSucceededEvent::getCommandName' => ['string'], +'MongoDB\Driver\Monitoring\CommandSucceededEvent::getDurationMicros' => ['int'], +'MongoDB\Driver\Monitoring\CommandSucceededEvent::getOperationId' => ['string'], +'MongoDB\Driver\Monitoring\CommandSucceededEvent::getReply' => ['object'], +'MongoDB\Driver\Monitoring\CommandSucceededEvent::getRequestId' => ['string'], +'MongoDB\Driver\Monitoring\CommandSucceededEvent::getServer' => ['MongoDB\Driver\Server'], +'MongoDB\Driver\Monitoring\CommandSucceededEvent::getServiceId' => ['?MongoDB\BSON\ObjectId'], +'MongoDB\Driver\Monitoring\CommandSucceededEvent::getServerConnectionId' => ['?int'], +'MongoDB\Driver\Monitoring\SDAMSubscriber::serverChanged' => ['void', 'event' => 'MongoDB\Driver\Monitoring\ServerChangedEvent'], +'MongoDB\Driver\Monitoring\SDAMSubscriber::serverClosed' => ['void', 'event' => 'MongoDB\Driver\Monitoring\ServerClosedEvent'], +'MongoDB\Driver\Monitoring\SDAMSubscriber::serverOpening' => ['void', 'event' => 'MongoDB\Driver\Monitoring\ServerOpeningEvent'], +'MongoDB\Driver\Monitoring\SDAMSubscriber::serverHeartbeatFailed' => ['void', 'event' => 'MongoDB\Driver\Monitoring\ServerHeartbeatFailedEvent'], +'MongoDB\Driver\Monitoring\SDAMSubscriber::serverHeartbeatStarted' => ['void', 'event' => 'MongoDB\Driver\Monitoring\ServerHeartbeatStartedEvent'], +'MongoDB\Driver\Monitoring\SDAMSubscriber::serverHeartbeatSucceeded' => ['void', 'event' => 'MongoDB\Driver\Monitoring\ServerHeartbeatSucceededEvent'], +'MongoDB\Driver\Monitoring\SDAMSubscriber::topologyChanged' => ['void', 'event' => 'MongoDB\Driver\Monitoring\TopologyChangedEvent'], +'MongoDB\Driver\Monitoring\SDAMSubscriber::topologyClosed' => ['void', 'event' => 'MongoDB\Driver\Monitoring\TopologyClosedEvent'], +'MongoDB\Driver\Monitoring\SDAMSubscriber::topologyOpening' => ['void', 'event' => 'MongoDB\Driver\Monitoring\TopologyOpeningEvent'], +'MongoDB\Driver\Monitoring\ServerChangedEvent::getPort' => ['int'], +'MongoDB\Driver\Monitoring\ServerChangedEvent::getHost' => ['string'], +'MongoDB\Driver\Monitoring\ServerChangedEvent::getNewDescription' => ['MongoDB\Driver\ServerDescription'], +'MongoDB\Driver\Monitoring\ServerChangedEvent::getPreviousDescription' => ['MongoDB\Driver\ServerDescription'], +'MongoDB\Driver\Monitoring\ServerChangedEvent::getTopologyId' => ['MongoDB\BSON\ObjectId'], +'MongoDB\Driver\Monitoring\ServerClosedEvent::getPort' => ['int'], +'MongoDB\Driver\Monitoring\ServerClosedEvent::getHost' => ['string'], +'MongoDB\Driver\Monitoring\ServerClosedEvent::getTopologyId' => ['MongoDB\BSON\ObjectId'], +'MongoDB\Driver\Monitoring\ServerHeartbeatFailedEvent::getDurationMicros' => ['int'], +'MongoDB\Driver\Monitoring\ServerHeartbeatFailedEvent::getError' => ['Exception'], +'MongoDB\Driver\Monitoring\ServerHeartbeatFailedEvent::getPort' => ['int'], +'MongoDB\Driver\Monitoring\ServerHeartbeatFailedEvent::getHost' => ['string'], +'MongoDB\Driver\Monitoring\ServerHeartbeatFailedEvent::isAwaited' => ['bool'], +'MongoDB\Driver\Monitoring\ServerHeartbeatStartedEvent::getPort' => ['int'], +'MongoDB\Driver\Monitoring\ServerHeartbeatStartedEvent::getHost' => ['string'], +'MongoDB\Driver\Monitoring\ServerHeartbeatStartedEvent::isAwaited' => ['bool'], +'MongoDB\Driver\Monitoring\ServerHeartbeatSucceededEvent::getDurationMicros' => ['int'], +'MongoDB\Driver\Monitoring\ServerHeartbeatSucceededEvent::getReply' => ['object'], +'MongoDB\Driver\Monitoring\ServerHeartbeatSucceededEvent::getPort' => ['int'], +'MongoDB\Driver\Monitoring\ServerHeartbeatSucceededEvent::getHost' => ['string'], +'MongoDB\Driver\Monitoring\ServerHeartbeatSucceededEvent::isAwaited' => ['bool'], +'MongoDB\Driver\Monitoring\ServerOpeningEvent::getPort' => ['int'], +'MongoDB\Driver\Monitoring\ServerOpeningEvent::getHost' => ['string'], +'MongoDB\Driver\Monitoring\ServerOpeningEvent::getTopologyId' => ['MongoDB\BSON\ObjectId'], +'MongoDB\Driver\Monitoring\TopologyChangedEvent::getNewDescription' => ['MongoDB\Driver\TopologyDescription'], +'MongoDB\Driver\Monitoring\TopologyChangedEvent::getPreviousDescription' => ['MongoDB\Driver\TopologyDescription'], +'MongoDB\Driver\Monitoring\TopologyChangedEvent::getTopologyId' => ['MongoDB\BSON\ObjectId'], +'MongoDB\Driver\Monitoring\TopologyClosedEvent::getTopologyId' => ['MongoDB\BSON\ObjectId'], +'MongoDB\Driver\Monitoring\TopologyOpeningEvent::getTopologyId' => ['MongoDB\BSON\ObjectId'], +'MongoDB\Driver\Query::__construct' => ['void', 'filter' => 'object|array', 'queryOptions=' => '?array'], +'MongoDB\Driver\ReadConcern::__construct' => ['void', 'level=' => '?string'], 'MongoDB\Driver\ReadConcern::getLevel' => ['?string'], 'MongoDB\Driver\ReadConcern::isDefault' => ['bool'], +'MongoDB\Driver\ReadConcern::bsonSerialize' => ['object|array'], 'MongoDB\Driver\ReadConcern::serialize' => ['string'], -'MongoDB\Driver\ReadConcern::unserialize' => ['void', 'serialized'=>'string'], -'MongoDB\Driver\ReadPreference::__construct' => ['void', 'mode'=>'string|int', 'tagSets='=>'array', 'options='=>'array'], -'MongoDB\Driver\ReadPreference::bsonSerialize' => ['object'], -'MongoDB\Driver\ReadPreference::getHedge' => ['object|null'], +'MongoDB\Driver\ReadConcern::unserialize' => ['void', 'serialized' => ''], +'MongoDB\Driver\ReadPreference::__construct' => ['void', 'mode' => 'string|int', 'tagSets=' => '?array', 'options=' => '?array'], +'MongoDB\Driver\ReadPreference::getHedge' => ['?object'], 'MongoDB\Driver\ReadPreference::getMaxStalenessSeconds' => ['int'], 'MongoDB\Driver\ReadPreference::getMode' => ['int'], 'MongoDB\Driver\ReadPreference::getModeString' => ['string'], 'MongoDB\Driver\ReadPreference::getTagSets' => ['array'], +'MongoDB\Driver\ReadPreference::bsonSerialize' => ['object|array'], 'MongoDB\Driver\ReadPreference::serialize' => ['string'], -'MongoDB\Driver\ReadPreference::unserialize' => ['void', 'serialized'=>'string'], -'MongoDB\Driver\Server::__construct' => ['void', 'host'=>'string', 'port'=>'string', 'options='=>'array', 'driverOptions='=>'array'], -'MongoDB\Driver\Server::executeBulkWrite' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'zwrite'=>'BulkWrite'], -'MongoDB\Driver\Server::executeCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command'], -'MongoDB\Driver\Server::executeQuery' => ['MongoDB\Driver\Cursor', 'namespace'=>'string', 'zquery'=>'Query'], -'mongodb\driver\server::executeReadCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'array'], -'mongodb\driver\server::executeReadWriteCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'array'], -'mongodb\driver\server::executeWriteCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'array'], +'MongoDB\Driver\ReadPreference::unserialize' => ['void', 'serialized' => ''], +'MongoDB\Driver\Server::executeBulkWrite' => ['MongoDB\Driver\WriteResult', 'namespace' => 'string', 'bulkWrite' => 'MongoDB\Driver\BulkWrite', 'options=' => 'MongoDB\Driver\WriteConcern|array|null'], +'MongoDB\Driver\Server::executeCommand' => ['MongoDB\Driver\Cursor', 'db' => 'string', 'command' => 'MongoDB\Driver\Command', 'options=' => 'MongoDB\Driver\ReadPreference|array|null'], +'MongoDB\Driver\Server::executeQuery' => ['MongoDB\Driver\Cursor', 'namespace' => 'string', 'query' => 'MongoDB\Driver\Query', 'options=' => 'MongoDB\Driver\ReadPreference|array|null'], +'MongoDB\Driver\Server::executeReadCommand' => ['MongoDB\Driver\Cursor', 'db' => 'string', 'command' => 'MongoDB\Driver\Command', 'options=' => '?array'], +'MongoDB\Driver\Server::executeReadWriteCommand' => ['MongoDB\Driver\Cursor', 'db' => 'string', 'command' => 'MongoDB\Driver\Command', 'options=' => '?array'], +'MongoDB\Driver\Server::executeWriteCommand' => ['MongoDB\Driver\Cursor', 'db' => 'string', 'command' => 'MongoDB\Driver\Command', 'options=' => '?array'], 'MongoDB\Driver\Server::getHost' => ['string'], 'MongoDB\Driver\Server::getInfo' => ['array'], -'MongoDB\Driver\Server::getLatency' => ['int'], +'MongoDB\Driver\Server::getLatency' => ['?int'], 'MongoDB\Driver\Server::getPort' => ['int'], -'MongoDB\Driver\Server::getState' => [''], +'MongoDB\Driver\Server::getServerDescription' => ['MongoDB\Driver\ServerDescription'], 'MongoDB\Driver\Server::getTags' => ['array'], 'MongoDB\Driver\Server::getType' => ['int'], 'MongoDB\Driver\Server::isArbiter' => ['bool'], -'MongoDB\Driver\Server::isDelayed' => [''], 'MongoDB\Driver\Server::isHidden' => ['bool'], 'MongoDB\Driver\Server::isPassive' => ['bool'], 'MongoDB\Driver\Server::isPrimary' => ['bool'], 'MongoDB\Driver\Server::isSecondary' => ['bool'], -'mongodb\driver\session::__construct' => ['void'], -'mongodb\driver\session::abortTransaction' => ['void'], -'mongodb\driver\session::advanceClusterTime' => ['void', 'clusterTime'=>'array|object'], -'mongodb\driver\session::advanceOperationTime' => ['void', 'operationTime'=>'MongoDB\BSON\TimestampInterface'], -'mongodb\driver\session::commitTransaction' => ['void'], -'mongodb\driver\session::endSession' => ['void'], -'mongodb\driver\session::getClusterTime' => ['?object'], -'mongodb\driver\session::getLogicalSessionId' => ['object'], -'mongodb\driver\session::getOperationTime' => ['MongoDB\BSON\Timestamp|null'], -'mongodb\driver\session::getTransactionOptions' => ['array|null'], -'mongodb\driver\session::getTransactionState' => ['string'], -'mongodb\driver\session::startTransaction' => ['void', 'options'=>'array|object'], -'MongoDB\Driver\WriteConcern::__construct' => ['void', 'wstring'=>'string|int', 'wtimeout='=>'int', 'journal='=>'bool'], -'MongoDB\Driver\WriteConcern::bsonSerialize' => ['object'], +'MongoDB\Driver\ServerApi::__construct' => ['void', 'version' => 'string', 'strict=' => '?bool', 'deprecationErrors=' => '?bool'], +'MongoDB\Driver\ServerApi::bsonSerialize' => ['object|array'], +'MongoDB\Driver\ServerApi::serialize' => ['string'], +'MongoDB\Driver\ServerApi::unserialize' => ['void', 'serialized' => ''], +'MongoDB\Driver\ServerDescription::getHelloResponse' => ['array'], +'MongoDB\Driver\ServerDescription::getHost' => ['string'], +'MongoDB\Driver\ServerDescription::getLastUpdateTime' => ['int'], +'MongoDB\Driver\ServerDescription::getPort' => ['int'], +'MongoDB\Driver\ServerDescription::getRoundTripTime' => ['?int'], +'MongoDB\Driver\ServerDescription::getType' => ['string'], +'MongoDB\Driver\Session::abortTransaction' => ['void'], +'MongoDB\Driver\Session::advanceClusterTime' => ['void', 'clusterTime' => 'object|array'], +'MongoDB\Driver\Session::advanceOperationTime' => ['void', 'operationTime' => 'MongoDB\BSON\TimestampInterface'], +'MongoDB\Driver\Session::commitTransaction' => ['void'], +'MongoDB\Driver\Session::endSession' => ['void'], +'MongoDB\Driver\Session::getClusterTime' => ['?object'], +'MongoDB\Driver\Session::getLogicalSessionId' => ['object'], +'MongoDB\Driver\Session::getOperationTime' => ['?MongoDB\BSON\Timestamp'], +'MongoDB\Driver\Session::getServer' => ['?MongoDB\Driver\Server'], +'MongoDB\Driver\Session::getTransactionOptions' => ['?array'], +'MongoDB\Driver\Session::getTransactionState' => ['string'], +'MongoDB\Driver\Session::isDirty' => ['bool'], +'MongoDB\Driver\Session::isInTransaction' => ['bool'], +'MongoDB\Driver\Session::startTransaction' => ['void', 'options=' => '?array'], +'MongoDB\Driver\TopologyDescription::getServers' => ['array'], +'MongoDB\Driver\TopologyDescription::getType' => ['string'], +'MongoDB\Driver\TopologyDescription::hasReadableServer' => ['bool', 'readPreference=' => '?MongoDB\Driver\ReadPreference'], +'MongoDB\Driver\TopologyDescription::hasWritableServer' => ['bool'], +'MongoDB\Driver\WriteConcern::__construct' => ['void', 'w' => 'string|int', 'wtimeout=' => '?int', 'journal=' => '?bool'], 'MongoDB\Driver\WriteConcern::getJournal' => ['?bool'], -'MongoDB\Driver\WriteConcern::getJurnal' => ['?bool'], -'MongoDB\Driver\WriteConcern::getW' => ['int|null|string'], +'MongoDB\Driver\WriteConcern::getW' => ['string|int|null'], 'MongoDB\Driver\WriteConcern::getWtimeout' => ['int'], 'MongoDB\Driver\WriteConcern::isDefault' => ['bool'], +'MongoDB\Driver\WriteConcern::bsonSerialize' => ['object|array'], 'MongoDB\Driver\WriteConcern::serialize' => ['string'], -'MongoDB\Driver\WriteConcern::unserialize' => ['void', 'serialized'=>'string'], +'MongoDB\Driver\WriteConcern::unserialize' => ['void', 'serialized' => ''], 'MongoDB\Driver\WriteConcernError::getCode' => ['int'], -'MongoDB\Driver\WriteConcernError::getInfo' => ['mixed'], +'MongoDB\Driver\WriteConcernError::getInfo' => ['?object'], 'MongoDB\Driver\WriteConcernError::getMessage' => ['string'], 'MongoDB\Driver\WriteError::getCode' => ['int'], 'MongoDB\Driver\WriteError::getIndex' => ['int'], -'MongoDB\Driver\WriteError::getInfo' => ['mixed'], +'MongoDB\Driver\WriteError::getInfo' => ['?object'], 'MongoDB\Driver\WriteError::getMessage' => ['string'], -'MongoDB\Driver\WriteException::getWriteResult' => [''], -'MongoDB\Driver\WriteResult::getDeletedCount' => ['?int'], -'MongoDB\Driver\WriteResult::getInfo' => [''], 'MongoDB\Driver\WriteResult::getInsertedCount' => ['?int'], 'MongoDB\Driver\WriteResult::getMatchedCount' => ['?int'], 'MongoDB\Driver\WriteResult::getModifiedCount' => ['?int'], -'MongoDB\Driver\WriteResult::getServer' => ['MongoDB\Driver\Server'], +'MongoDB\Driver\WriteResult::getDeletedCount' => ['?int'], 'MongoDB\Driver\WriteResult::getUpsertedCount' => ['?int'], +'MongoDB\Driver\WriteResult::getServer' => ['MongoDB\Driver\Server'], 'MongoDB\Driver\WriteResult::getUpsertedIds' => ['array'], -'MongoDB\Driver\WriteResult::getWriteConcernError' => ['MongoDB\Driver\WriteConcernError|null'], -'MongoDB\Driver\WriteResult::getWriteErrors' => ['MongoDB\Driver\WriteError[]'], +'MongoDB\Driver\WriteResult::getWriteConcernError' => ['?MongoDB\Driver\WriteConcernError'], +'MongoDB\Driver\WriteResult::getWriteErrors' => ['array'], 'MongoDB\Driver\WriteResult::isAcknowledged' => ['bool'], 'MongoDBRef::create' => ['array', 'collection'=>'string', 'id'=>'mixed', 'database='=>'string'], 'MongoDBRef::get' => ['?array', 'db'=>'MongoDB', 'ref'=>'array'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index f2bb87d83fa..f2bf1d49284 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -3965,213 +3965,319 @@ 'MongoDBRef::create' => ['array', 'collection'=>'string', 'id'=>'mixed', 'database='=>'string'], 'MongoDBRef::get' => ['?array', 'db'=>'MongoDB', 'ref'=>'array'], 'MongoDBRef::isRef' => ['bool', 'ref'=>'mixed'], - 'MongoDB\BSON\Binary::__construct' => ['void', 'data'=>'string', 'type'=>'int'], - 'MongoDB\BSON\Binary::__toString' => ['string'], + 'MongoDB\BSON\Binary::__construct' => ['void', 'data' => 'string', 'type' => 'int'], 'MongoDB\BSON\Binary::getData' => ['string'], 'MongoDB\BSON\Binary::getType' => ['int'], - 'MongoDB\BSON\Decimal128::__construct' => ['void', 'value='=>'string'], + 'MongoDB\BSON\Binary::__toString' => ['string'], + 'MongoDB\BSON\Binary::serialize' => ['string'], + 'MongoDB\BSON\Binary::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\Binary::jsonSerialize' => ['mixed'], + 'MongoDB\BSON\BinaryInterface::getData' => ['void'], + 'MongoDB\BSON\BinaryInterface::getType' => ['void'], + 'MongoDB\BSON\BinaryInterface::__toString' => ['string'], + 'MongoDB\BSON\DBPointer::__toString' => ['string'], + 'MongoDB\BSON\DBPointer::serialize' => ['string'], + 'MongoDB\BSON\DBPointer::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\DBPointer::jsonSerialize' => ['mixed'], + 'MongoDB\BSON\Decimal128::__construct' => ['void', 'value' => 'string'], 'MongoDB\BSON\Decimal128::__toString' => ['string'], - 'MongoDB\BSON\Javascript::__construct' => ['void', 'code'=>'string', 'scope='=>'array|object'], - 'MongoDB\BSON\ObjectId::__construct' => ['void', 'id='=>'string'], + 'MongoDB\BSON\Decimal128::serialize' => ['string'], + 'MongoDB\BSON\Decimal128::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\Decimal128::jsonSerialize' => ['mixed'], + 'MongoDB\BSON\Decimal128Interface::__toString' => ['void'], + 'MongoDB\BSON\Int64::__toString' => ['string'], + 'MongoDB\BSON\Int64::serialize' => ['string'], + 'MongoDB\BSON\Int64::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\Int64::jsonSerialize' => ['mixed'], + 'MongoDB\BSON\Javascript::__construct' => ['void', 'code' => 'string', 'scope=' => 'object|array|null'], + 'MongoDB\BSON\Javascript::getCode' => ['string'], + 'MongoDB\BSON\Javascript::getScope' => ['?object'], + 'MongoDB\BSON\Javascript::__toString' => ['string'], + 'MongoDB\BSON\Javascript::serialize' => ['string'], + 'MongoDB\BSON\Javascript::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\Javascript::jsonSerialize' => ['mixed'], + 'MongoDB\BSON\JavascriptInterface::getCode' => ['void'], + 'MongoDB\BSON\JavascriptInterface::getScope' => ['void'], + 'MongoDB\BSON\JavascriptInterface::__toString' => ['void'], + 'MongoDB\BSON\MaxKey::serialize' => ['string'], + 'MongoDB\BSON\MaxKey::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\MaxKey::jsonSerialize' => ['mixed'], + 'MongoDB\BSON\MinKey::serialize' => ['string'], + 'MongoDB\BSON\MinKey::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\MinKey::jsonSerialize' => ['mixed'], + 'MongoDB\BSON\ObjectId::__construct' => ['void', 'id=' => '?string'], + 'MongoDB\BSON\ObjectId::getTimestamp' => ['int'], 'MongoDB\BSON\ObjectId::__toString' => ['string'], - 'MongoDB\BSON\Regex::__construct' => ['void', 'pattern'=>'string', 'flags='=>'string'], - 'MongoDB\BSON\Regex::__toString' => ['string'], - 'MongoDB\BSON\Regex::getFlags' => ['string'], + 'MongoDB\BSON\ObjectId::serialize' => ['string'], + 'MongoDB\BSON\ObjectId::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\ObjectId::jsonSerialize' => ['mixed'], + 'MongoDB\BSON\ObjectIdInterface::getTimestamp' => ['void'], + 'MongoDB\BSON\ObjectIdInterface::__toString' => ['void'], + 'MongoDB\BSON\Regex::__construct' => ['void', 'pattern' => 'string', 'flags=' => 'string'], 'MongoDB\BSON\Regex::getPattern' => ['string'], - 'MongoDB\BSON\Serializable::bsonSerialize' => ['array|object'], - 'MongoDB\BSON\Timestamp::__construct' => ['void', 'increment'=>'int', 'timestamp'=>'int'], + 'MongoDB\BSON\Regex::getFlags' => ['string'], + 'MongoDB\BSON\Regex::__toString' => ['string'], + 'MongoDB\BSON\Regex::serialize' => ['string'], + 'MongoDB\BSON\Regex::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\Regex::jsonSerialize' => ['mixed'], + 'MongoDB\BSON\RegexInterface::getPattern' => ['void'], + 'MongoDB\BSON\RegexInterface::getFlags' => ['void'], + 'MongoDB\BSON\RegexInterface::__toString' => ['void'], + 'MongoDB\BSON\Serializable::bsonSerialize' => ['void'], + 'MongoDB\BSON\Symbol::__toString' => ['string'], + 'MongoDB\BSON\Symbol::serialize' => ['string'], + 'MongoDB\BSON\Symbol::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\Symbol::jsonSerialize' => ['mixed'], + 'MongoDB\BSON\Timestamp::__construct' => ['void', 'increment' => 'string|int', 'timestamp' => 'string|int'], + 'MongoDB\BSON\Timestamp::getTimestamp' => ['int'], + 'MongoDB\BSON\Timestamp::getIncrement' => ['int'], 'MongoDB\BSON\Timestamp::__toString' => ['string'], - 'MongoDB\BSON\UTCDateTime::__construct' => ['void', 'milliseconds='=>'int|DateTimeInterface'], - 'MongoDB\BSON\UTCDateTime::__toString' => ['string'], + 'MongoDB\BSON\Timestamp::serialize' => ['string'], + 'MongoDB\BSON\Timestamp::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\Timestamp::jsonSerialize' => ['mixed'], + 'MongoDB\BSON\TimestampInterface::getTimestamp' => ['void'], + 'MongoDB\BSON\TimestampInterface::getIncrement' => ['void'], + 'MongoDB\BSON\TimestampInterface::__toString' => ['void'], + 'MongoDB\BSON\UTCDateTime::__construct' => ['void', 'milliseconds=' => 'DateTimeInterface|string|int|float|null'], 'MongoDB\BSON\UTCDateTime::toDateTime' => ['DateTime'], - 'MongoDB\BSON\Unserializable::bsonUnserialize' => ['void', 'data'=>'array'], - 'MongoDB\BSON\binary::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\binary::serialize' => ['string'], - 'MongoDB\BSON\binary::unserialize' => ['void', 'serialized'=>'string'], - 'MongoDB\BSON\binaryinterface::__toString' => ['string'], - 'MongoDB\BSON\binaryinterface::getData' => ['string'], - 'MongoDB\BSON\binaryinterface::getType' => ['int'], - 'MongoDB\BSON\dbpointer::__construct' => ['void'], - 'MongoDB\BSON\dbpointer::__toString' => ['string'], - 'MongoDB\BSON\dbpointer::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\dbpointer::serialize' => ['string'], - 'MongoDB\BSON\dbpointer::unserialize' => ['void', 'serialized'=>'string'], - 'MongoDB\BSON\decimal128::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\decimal128::serialize' => ['string'], - 'MongoDB\BSON\decimal128::unserialize' => ['void', 'serialized'=>'string'], - 'MongoDB\BSON\decimal128interface::__toString' => ['string'], - 'MongoDB\BSON\fromJSON' => ['string', 'json'=>'string'], - 'MongoDB\BSON\fromPHP' => ['string', 'value'=>'array|object'], - 'MongoDB\BSON\int64::__construct' => ['void'], - 'MongoDB\BSON\int64::__toString' => ['string'], - 'MongoDB\BSON\int64::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\int64::serialize' => ['string'], - 'MongoDB\BSON\int64::unserialize' => ['void', 'serialized'=>'string'], - 'MongoDB\BSON\javascript::__toString' => ['string'], - 'MongoDB\BSON\javascript::getCode' => ['string'], - 'MongoDB\BSON\javascript::getScope' => ['?object'], - 'MongoDB\BSON\javascript::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\javascript::serialize' => ['string'], - 'MongoDB\BSON\javascript::unserialize' => ['void', 'serialized'=>'string'], - 'MongoDB\BSON\javascriptinterface::__toString' => ['string'], - 'MongoDB\BSON\javascriptinterface::getCode' => ['string'], - 'MongoDB\BSON\javascriptinterface::getScope' => ['?object'], - 'MongoDB\BSON\maxkey::__construct' => ['void'], - 'MongoDB\BSON\maxkey::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\maxkey::serialize' => ['string'], - 'MongoDB\BSON\maxkey::unserialize' => ['void', 'serialized'=>'string'], - 'MongoDB\BSON\minkey::__construct' => ['void'], - 'MongoDB\BSON\minkey::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\minkey::serialize' => ['string'], - 'MongoDB\BSON\minkey::unserialize' => ['void', 'serialized'=>'string'], - 'MongoDB\BSON\objectid::getTimestamp' => ['int'], - 'MongoDB\BSON\objectid::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\objectid::serialize' => ['string'], - 'MongoDB\BSON\objectid::unserialize' => ['void', 'serialized'=>'string'], - 'MongoDB\BSON\objectidinterface::__toString' => ['string'], - 'MongoDB\BSON\objectidinterface::getTimestamp' => ['int'], - 'MongoDB\BSON\regex::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\regex::serialize' => ['string'], - 'MongoDB\BSON\regex::unserialize' => ['void', 'serialized'=>'string'], - 'MongoDB\BSON\regexinterface::__toString' => ['string'], - 'MongoDB\BSON\regexinterface::getFlags' => ['string'], - 'MongoDB\BSON\regexinterface::getPattern' => ['string'], - 'MongoDB\BSON\symbol::__construct' => ['void'], - 'MongoDB\BSON\symbol::__toString' => ['string'], - 'MongoDB\BSON\symbol::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\symbol::serialize' => ['string'], - 'MongoDB\BSON\symbol::unserialize' => ['void', 'serialized'=>'string'], - 'MongoDB\BSON\timestamp::getIncrement' => ['int'], - 'MongoDB\BSON\timestamp::getTimestamp' => ['int'], - 'MongoDB\BSON\timestamp::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\timestamp::serialize' => ['string'], - 'MongoDB\BSON\timestamp::unserialize' => ['void', 'serialized'=>'string'], - 'MongoDB\BSON\timestampinterface::__toString' => ['string'], - 'MongoDB\BSON\timestampinterface::getIncrement' => ['int'], - 'MongoDB\BSON\timestampinterface::getTimestamp' => ['int'], - 'MongoDB\BSON\toJSON' => ['string', 'bson'=>'string'], - 'MongoDB\BSON\toPHP' => ['object', 'bson'=>'string', 'typeMap='=>'array'], - 'MongoDB\BSON\undefined::__construct' => ['void'], - 'MongoDB\BSON\undefined::__toString' => ['string'], - 'MongoDB\BSON\undefined::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\undefined::serialize' => ['string'], - 'MongoDB\BSON\undefined::unserialize' => ['void', 'serialized'=>'string'], - 'MongoDB\BSON\utcdatetime::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\utcdatetime::serialize' => ['string'], - 'MongoDB\BSON\utcdatetime::unserialize' => ['void', 'serialized'=>'string'], - 'MongoDB\BSON\utcdatetimeinterface::__toString' => ['string'], - 'MongoDB\BSON\utcdatetimeinterface::toDateTime' => ['DateTime'], - 'MongoDB\Driver\BulkWrite::__construct' => ['void', 'ordered='=>'bool'], + 'MongoDB\BSON\UTCDateTime::__toString' => ['string'], + 'MongoDB\BSON\UTCDateTime::serialize' => ['string'], + 'MongoDB\BSON\UTCDateTime::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\UTCDateTime::jsonSerialize' => ['mixed'], + 'MongoDB\BSON\UTCDateTimeInterface::toDateTime' => ['void'], + 'MongoDB\BSON\UTCDateTimeInterface::__toString' => ['void'], + 'MongoDB\BSON\Undefined::__toString' => ['string'], + 'MongoDB\BSON\Undefined::serialize' => ['string'], + 'MongoDB\BSON\Undefined::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\Undefined::jsonSerialize' => ['mixed'], + 'MongoDB\BSON\Unserializable::bsonUnserialize' => ['void', 'data' => 'array'], + 'MongoDB\Driver\BulkWrite::__construct' => ['void', 'options=' => '?array'], 'MongoDB\Driver\BulkWrite::count' => ['int'], - 'MongoDB\Driver\BulkWrite::delete' => ['void', 'filter'=>'array|object', 'deleteOptions='=>'array'], - 'MongoDB\Driver\BulkWrite::insert' => ['void|MongoDB\BSON\ObjectId', 'document'=>'array|object'], - 'MongoDB\Driver\BulkWrite::update' => ['void', 'filter'=>'array|object', 'newObj'=>'array|object', 'updateOptions='=>'array'], - 'MongoDB\Driver\Command::__construct' => ['void', 'document'=>'array|object'], - 'MongoDB\Driver\Cursor::__construct' => ['void', 'server'=>'Server', 'responseDocument'=>'string'], + 'MongoDB\Driver\BulkWrite::delete' => ['void', 'filter' => 'object|array', 'deleteOptions=' => '?array'], + 'MongoDB\Driver\BulkWrite::insert' => ['mixed', 'document' => 'object|array'], + 'MongoDB\Driver\BulkWrite::update' => ['void', 'filter' => 'object|array', 'newObj' => 'object|array', 'updateOptions=' => '?array'], + 'MongoDB\Driver\ClientEncryption::__construct' => ['void', 'options' => 'array'], + 'MongoDB\Driver\ClientEncryption::addKeyAltName' => ['?object', 'keyId' => 'MongoDB\BSON\Binary', 'keyAltName' => 'string'], + 'MongoDB\Driver\ClientEncryption::createDataKey' => ['MongoDB\BSON\Binary', 'kmsProvider' => 'string', 'options=' => '?array'], + 'MongoDB\Driver\ClientEncryption::decrypt' => ['mixed', 'value' => 'MongoDB\BSON\Binary'], + 'MongoDB\Driver\ClientEncryption::deleteKey' => ['object', 'keyId' => 'MongoDB\BSON\Binary'], + 'MongoDB\Driver\ClientEncryption::encrypt' => ['MongoDB\BSON\Binary', 'value' => 'mixed', 'options=' => '?array'], + 'MongoDB\Driver\ClientEncryption::getKey' => ['?object', 'keyId' => 'MongoDB\BSON\Binary'], + 'MongoDB\Driver\ClientEncryption::getKeyByAltName' => ['?object', 'keyAltName' => 'string'], + 'MongoDB\Driver\ClientEncryption::getKeys' => ['MongoDB\Driver\Cursor'], + 'MongoDB\Driver\ClientEncryption::removeKeyAltName' => ['?object', 'keyId' => 'MongoDB\BSON\Binary', 'keyAltName' => 'string'], + 'MongoDB\Driver\ClientEncryption::rewrapManyDataKey' => ['object', 'filter' => 'object|array', 'options=' => '?array'], + 'MongoDB\Driver\Command::__construct' => ['void', 'document' => 'object|array', 'commandOptions=' => '?array'], + 'MongoDB\Driver\Cursor::current' => ['object|array|null'], 'MongoDB\Driver\Cursor::getId' => ['MongoDB\Driver\CursorId'], 'MongoDB\Driver\Cursor::getServer' => ['MongoDB\Driver\Server'], 'MongoDB\Driver\Cursor::isDead' => ['bool'], - 'MongoDB\Driver\Cursor::setTypeMap' => ['void', 'typemap'=>'array'], + 'MongoDB\Driver\Cursor::key' => ['?int'], + 'MongoDB\Driver\Cursor::next' => ['void'], + 'MongoDB\Driver\Cursor::rewind' => ['void'], + 'MongoDB\Driver\Cursor::setTypeMap' => ['void', 'typemap' => 'array'], 'MongoDB\Driver\Cursor::toArray' => ['array'], - 'MongoDB\Driver\CursorId::__construct' => ['void', 'id'=>'string'], + 'MongoDB\Driver\Cursor::valid' => ['bool'], 'MongoDB\Driver\CursorId::__toString' => ['string'], 'MongoDB\Driver\CursorId::serialize' => ['string'], - 'MongoDB\Driver\CursorId::unserialize' => ['void', 'serialized'=>'string'], - 'MongoDB\Driver\Exception\RuntimeException::__clone' => ['void'], - 'MongoDB\Driver\Exception\RuntimeException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?RuntimeException|?Throwable'], + 'MongoDB\Driver\CursorId::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\Driver\CursorInterface::current' => ['object|array|null'], + 'MongoDB\Driver\CursorInterface::getId' => ['void'], + 'MongoDB\Driver\CursorInterface::getServer' => ['void'], + 'MongoDB\Driver\CursorInterface::isDead' => ['void'], + 'MongoDB\Driver\CursorInterface::key' => ['?int'], + 'MongoDB\Driver\CursorInterface::next' => ['void'], + 'MongoDB\Driver\CursorInterface::rewind' => ['void'], + 'MongoDB\Driver\CursorInterface::setTypeMap' => ['void', 'typemap' => 'array'], + 'MongoDB\Driver\CursorInterface::toArray' => ['void'], + 'MongoDB\Driver\CursorInterface::valid' => ['bool'], + 'MongoDB\Driver\Exception\AuthenticationException::__toString' => ['string'], + 'MongoDB\Driver\Exception\BulkWriteException::__toString' => ['string'], + 'MongoDB\Driver\Exception\CommandException::getResultDocument' => ['object'], + 'MongoDB\Driver\Exception\CommandException::__toString' => ['string'], + 'MongoDB\Driver\Exception\ConnectionException::__toString' => ['string'], + 'MongoDB\Driver\Exception\ConnectionTimeoutException::__toString' => ['string'], + 'MongoDB\Driver\Exception\EncryptionException::__toString' => ['string'], + 'MongoDB\Driver\Exception\Exception::__toString' => ['string'], + 'MongoDB\Driver\Exception\ExecutionTimeoutException::__toString' => ['string'], + 'MongoDB\Driver\Exception\InvalidArgumentException::__toString' => ['string'], + 'MongoDB\Driver\Exception\LogicException::__toString' => ['string'], + 'MongoDB\Driver\Exception\RuntimeException::hasErrorLabel' => ['bool', 'errorLabel' => 'string'], 'MongoDB\Driver\Exception\RuntimeException::__toString' => ['string'], - 'MongoDB\Driver\Exception\RuntimeException::__wakeup' => ['void'], - 'MongoDB\Driver\Exception\RuntimeException::getCode' => ['int'], - 'MongoDB\Driver\Exception\RuntimeException::getFile' => ['string'], - 'MongoDB\Driver\Exception\RuntimeException::getLine' => ['int'], - 'MongoDB\Driver\Exception\RuntimeException::getMessage' => ['string'], - 'MongoDB\Driver\Exception\RuntimeException::getPrevious' => ['RuntimeException|Throwable'], - 'MongoDB\Driver\Exception\RuntimeException::getTrace' => ['list\',args?:array}>'], - 'MongoDB\Driver\Exception\RuntimeException::getTraceAsString' => ['string'], - 'MongoDB\Driver\Exception\WriteException::__clone' => ['void'], - 'MongoDB\Driver\Exception\WriteException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?RuntimeException|?Throwable'], - 'MongoDB\Driver\Exception\WriteException::__toString' => ['string'], - 'MongoDB\Driver\Exception\WriteException::__wakeup' => ['void'], - 'MongoDB\Driver\Exception\WriteException::getCode' => ['int'], - 'MongoDB\Driver\Exception\WriteException::getFile' => ['string'], - 'MongoDB\Driver\Exception\WriteException::getLine' => ['int'], - 'MongoDB\Driver\Exception\WriteException::getMessage' => ['string'], - 'MongoDB\Driver\Exception\WriteException::getPrevious' => ['RuntimeException|Throwable'], - 'MongoDB\Driver\Exception\WriteException::getTrace' => ['list\',args?:array}>'], - 'MongoDB\Driver\Exception\WriteException::getTraceAsString' => ['string'], + 'MongoDB\Driver\Exception\SSLConnectionException::__toString' => ['string'], + 'MongoDB\Driver\Exception\ServerException::__toString' => ['string'], + 'MongoDB\Driver\Exception\UnexpectedValueException::__toString' => ['string'], 'MongoDB\Driver\Exception\WriteException::getWriteResult' => ['MongoDB\Driver\WriteResult'], - 'MongoDB\Driver\Manager::__construct' => ['void', 'uri'=>'string', 'options='=>'array', 'driverOptions='=>'array'], - 'MongoDB\Driver\Manager::executeBulkWrite' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'bulk'=>'MongoDB\Driver\BulkWrite', 'writeConcern='=>'MongoDB\Driver\WriteConcern'], - 'MongoDB\Driver\Manager::executeCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'readPreference='=>'MongoDB\Driver\ReadPreference'], - 'MongoDB\Driver\Manager::executeDelete' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'filter'=>'array|object', 'deleteOptions='=>'array', 'writeConcern='=>'MongoDB\Driver\WriteConcern'], - 'MongoDB\Driver\Manager::executeInsert' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'document'=>'array|object', 'writeConcern='=>'MongoDB\Driver\WriteConcern'], - 'MongoDB\Driver\Manager::executeQuery' => ['MongoDB\Driver\Cursor', 'namespace'=>'string', 'query'=>'MongoDB\Driver\Query', 'readPreference='=>'MongoDB\Driver\ReadPreference'], - 'MongoDB\Driver\Manager::executeUpdate' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'filter'=>'array|object', 'newObj'=>'array|object', 'updateOptions='=>'array', 'writeConcern='=>'MongoDB\Driver\WriteConcern'], + 'MongoDB\Driver\Exception\WriteException::__toString' => ['string'], + 'MongoDB\Driver\Manager::__construct' => ['void', 'uri=' => '?string', 'uriOptions=' => '?array', 'driverOptions=' => '?array'], + 'MongoDB\Driver\Manager::addSubscriber' => ['void', 'subscriber' => 'MongoDB\Driver\Monitoring\Subscriber'], + 'MongoDB\Driver\Manager::createClientEncryption' => ['MongoDB\Driver\ClientEncryption', 'options' => 'array'], + 'MongoDB\Driver\Manager::executeBulkWrite' => ['MongoDB\Driver\WriteResult', 'namespace' => 'string', 'bulk' => 'MongoDB\Driver\BulkWrite', 'options=' => 'MongoDB\Driver\WriteConcern|array|null'], + 'MongoDB\Driver\Manager::executeCommand' => ['MongoDB\Driver\Cursor', 'db' => 'string', 'command' => 'MongoDB\Driver\Command', 'options=' => 'MongoDB\Driver\ReadPreference|array|null'], + 'MongoDB\Driver\Manager::executeQuery' => ['MongoDB\Driver\Cursor', 'namespace' => 'string', 'query' => 'MongoDB\Driver\Query', 'options=' => 'MongoDB\Driver\ReadPreference|array|null'], + 'MongoDB\Driver\Manager::executeReadCommand' => ['MongoDB\Driver\Cursor', 'db' => 'string', 'command' => 'MongoDB\Driver\Command', 'options=' => '?array'], + 'MongoDB\Driver\Manager::executeReadWriteCommand' => ['MongoDB\Driver\Cursor', 'db' => 'string', 'command' => 'MongoDB\Driver\Command', 'options=' => '?array'], + 'MongoDB\Driver\Manager::executeWriteCommand' => ['MongoDB\Driver\Cursor', 'db' => 'string', 'command' => 'MongoDB\Driver\Command', 'options=' => '?array'], + 'MongoDB\Driver\Manager::getEncryptedFieldsMap' => ['object|array|null'], 'MongoDB\Driver\Manager::getReadConcern' => ['MongoDB\Driver\ReadConcern'], 'MongoDB\Driver\Manager::getReadPreference' => ['MongoDB\Driver\ReadPreference'], - 'MongoDB\Driver\Manager::getServers' => ['MongoDB\Driver\Server[]'], + 'MongoDB\Driver\Manager::getServers' => ['array'], 'MongoDB\Driver\Manager::getWriteConcern' => ['MongoDB\Driver\WriteConcern'], - 'MongoDB\Driver\Manager::selectServer' => ['MongoDB\Driver\Server', 'readPreference'=>'MongoDB\Driver\ReadPreference'], - 'MongoDB\Driver\Query::__construct' => ['void', 'filter'=>'array|object', 'queryOptions='=>'array'], - 'MongoDB\Driver\ReadConcern::__construct' => ['void', 'level='=>'string'], - 'MongoDB\Driver\ReadConcern::bsonSerialize' => ['object'], + 'MongoDB\Driver\Manager::removeSubscriber' => ['void', 'subscriber' => 'MongoDB\Driver\Monitoring\Subscriber'], + 'MongoDB\Driver\Manager::selectServer' => ['MongoDB\Driver\Server', 'readPreference=' => '?MongoDB\Driver\ReadPreference'], + 'MongoDB\Driver\Manager::startSession' => ['MongoDB\Driver\Session', 'options=' => '?array'], + 'MongoDB\Driver\Monitoring\CommandFailedEvent::getCommandName' => ['string'], + 'MongoDB\Driver\Monitoring\CommandFailedEvent::getDurationMicros' => ['int'], + 'MongoDB\Driver\Monitoring\CommandFailedEvent::getError' => ['Exception'], + 'MongoDB\Driver\Monitoring\CommandFailedEvent::getOperationId' => ['string'], + 'MongoDB\Driver\Monitoring\CommandFailedEvent::getReply' => ['object'], + 'MongoDB\Driver\Monitoring\CommandFailedEvent::getRequestId' => ['string'], + 'MongoDB\Driver\Monitoring\CommandFailedEvent::getServer' => ['MongoDB\Driver\Server'], + 'MongoDB\Driver\Monitoring\CommandFailedEvent::getServiceId' => ['?MongoDB\BSON\ObjectId'], + 'MongoDB\Driver\Monitoring\CommandFailedEvent::getServerConnectionId' => ['?int'], + 'MongoDB\Driver\Monitoring\CommandStartedEvent::getCommand' => ['object'], + 'MongoDB\Driver\Monitoring\CommandStartedEvent::getCommandName' => ['string'], + 'MongoDB\Driver\Monitoring\CommandStartedEvent::getDatabaseName' => ['string'], + 'MongoDB\Driver\Monitoring\CommandStartedEvent::getOperationId' => ['string'], + 'MongoDB\Driver\Monitoring\CommandStartedEvent::getRequestId' => ['string'], + 'MongoDB\Driver\Monitoring\CommandStartedEvent::getServer' => ['MongoDB\Driver\Server'], + 'MongoDB\Driver\Monitoring\CommandStartedEvent::getServiceId' => ['?MongoDB\BSON\ObjectId'], + 'MongoDB\Driver\Monitoring\CommandStartedEvent::getServerConnectionId' => ['?int'], + 'MongoDB\Driver\Monitoring\CommandSubscriber::commandStarted' => ['void', 'event' => 'MongoDB\Driver\Monitoring\CommandStartedEvent'], + 'MongoDB\Driver\Monitoring\CommandSubscriber::commandSucceeded' => ['void', 'event' => 'MongoDB\Driver\Monitoring\CommandSucceededEvent'], + 'MongoDB\Driver\Monitoring\CommandSubscriber::commandFailed' => ['void', 'event' => 'MongoDB\Driver\Monitoring\CommandFailedEvent'], + 'MongoDB\Driver\Monitoring\CommandSucceededEvent::getCommandName' => ['string'], + 'MongoDB\Driver\Monitoring\CommandSucceededEvent::getDurationMicros' => ['int'], + 'MongoDB\Driver\Monitoring\CommandSucceededEvent::getOperationId' => ['string'], + 'MongoDB\Driver\Monitoring\CommandSucceededEvent::getReply' => ['object'], + 'MongoDB\Driver\Monitoring\CommandSucceededEvent::getRequestId' => ['string'], + 'MongoDB\Driver\Monitoring\CommandSucceededEvent::getServer' => ['MongoDB\Driver\Server'], + 'MongoDB\Driver\Monitoring\CommandSucceededEvent::getServiceId' => ['?MongoDB\BSON\ObjectId'], + 'MongoDB\Driver\Monitoring\CommandSucceededEvent::getServerConnectionId' => ['?int'], + 'MongoDB\Driver\Monitoring\SDAMSubscriber::serverChanged' => ['void', 'event' => 'MongoDB\Driver\Monitoring\ServerChangedEvent'], + 'MongoDB\Driver\Monitoring\SDAMSubscriber::serverClosed' => ['void', 'event' => 'MongoDB\Driver\Monitoring\ServerClosedEvent'], + 'MongoDB\Driver\Monitoring\SDAMSubscriber::serverOpening' => ['void', 'event' => 'MongoDB\Driver\Monitoring\ServerOpeningEvent'], + 'MongoDB\Driver\Monitoring\SDAMSubscriber::serverHeartbeatFailed' => ['void', 'event' => 'MongoDB\Driver\Monitoring\ServerHeartbeatFailedEvent'], + 'MongoDB\Driver\Monitoring\SDAMSubscriber::serverHeartbeatStarted' => ['void', 'event' => 'MongoDB\Driver\Monitoring\ServerHeartbeatStartedEvent'], + 'MongoDB\Driver\Monitoring\SDAMSubscriber::serverHeartbeatSucceeded' => ['void', 'event' => 'MongoDB\Driver\Monitoring\ServerHeartbeatSucceededEvent'], + 'MongoDB\Driver\Monitoring\SDAMSubscriber::topologyChanged' => ['void', 'event' => 'MongoDB\Driver\Monitoring\TopologyChangedEvent'], + 'MongoDB\Driver\Monitoring\SDAMSubscriber::topologyClosed' => ['void', 'event' => 'MongoDB\Driver\Monitoring\TopologyClosedEvent'], + 'MongoDB\Driver\Monitoring\SDAMSubscriber::topologyOpening' => ['void', 'event' => 'MongoDB\Driver\Monitoring\TopologyOpeningEvent'], + 'MongoDB\Driver\Monitoring\ServerChangedEvent::getPort' => ['int'], + 'MongoDB\Driver\Monitoring\ServerChangedEvent::getHost' => ['string'], + 'MongoDB\Driver\Monitoring\ServerChangedEvent::getNewDescription' => ['MongoDB\Driver\ServerDescription'], + 'MongoDB\Driver\Monitoring\ServerChangedEvent::getPreviousDescription' => ['MongoDB\Driver\ServerDescription'], + 'MongoDB\Driver\Monitoring\ServerChangedEvent::getTopologyId' => ['MongoDB\BSON\ObjectId'], + 'MongoDB\Driver\Monitoring\ServerClosedEvent::getPort' => ['int'], + 'MongoDB\Driver\Monitoring\ServerClosedEvent::getHost' => ['string'], + 'MongoDB\Driver\Monitoring\ServerClosedEvent::getTopologyId' => ['MongoDB\BSON\ObjectId'], + 'MongoDB\Driver\Monitoring\ServerHeartbeatFailedEvent::getDurationMicros' => ['int'], + 'MongoDB\Driver\Monitoring\ServerHeartbeatFailedEvent::getError' => ['Exception'], + 'MongoDB\Driver\Monitoring\ServerHeartbeatFailedEvent::getPort' => ['int'], + 'MongoDB\Driver\Monitoring\ServerHeartbeatFailedEvent::getHost' => ['string'], + 'MongoDB\Driver\Monitoring\ServerHeartbeatFailedEvent::isAwaited' => ['bool'], + 'MongoDB\Driver\Monitoring\ServerHeartbeatStartedEvent::getPort' => ['int'], + 'MongoDB\Driver\Monitoring\ServerHeartbeatStartedEvent::getHost' => ['string'], + 'MongoDB\Driver\Monitoring\ServerHeartbeatStartedEvent::isAwaited' => ['bool'], + 'MongoDB\Driver\Monitoring\ServerHeartbeatSucceededEvent::getDurationMicros' => ['int'], + 'MongoDB\Driver\Monitoring\ServerHeartbeatSucceededEvent::getReply' => ['object'], + 'MongoDB\Driver\Monitoring\ServerHeartbeatSucceededEvent::getPort' => ['int'], + 'MongoDB\Driver\Monitoring\ServerHeartbeatSucceededEvent::getHost' => ['string'], + 'MongoDB\Driver\Monitoring\ServerHeartbeatSucceededEvent::isAwaited' => ['bool'], + 'MongoDB\Driver\Monitoring\ServerOpeningEvent::getPort' => ['int'], + 'MongoDB\Driver\Monitoring\ServerOpeningEvent::getHost' => ['string'], + 'MongoDB\Driver\Monitoring\ServerOpeningEvent::getTopologyId' => ['MongoDB\BSON\ObjectId'], + 'MongoDB\Driver\Monitoring\TopologyChangedEvent::getNewDescription' => ['MongoDB\Driver\TopologyDescription'], + 'MongoDB\Driver\Monitoring\TopologyChangedEvent::getPreviousDescription' => ['MongoDB\Driver\TopologyDescription'], + 'MongoDB\Driver\Monitoring\TopologyChangedEvent::getTopologyId' => ['MongoDB\BSON\ObjectId'], + 'MongoDB\Driver\Monitoring\TopologyClosedEvent::getTopologyId' => ['MongoDB\BSON\ObjectId'], + 'MongoDB\Driver\Monitoring\TopologyOpeningEvent::getTopologyId' => ['MongoDB\BSON\ObjectId'], + 'MongoDB\Driver\Query::__construct' => ['void', 'filter' => 'object|array', 'queryOptions=' => '?array'], + 'MongoDB\Driver\ReadConcern::__construct' => ['void', 'level=' => '?string'], 'MongoDB\Driver\ReadConcern::getLevel' => ['?string'], 'MongoDB\Driver\ReadConcern::isDefault' => ['bool'], + 'MongoDB\Driver\ReadConcern::bsonSerialize' => ['object|array'], 'MongoDB\Driver\ReadConcern::serialize' => ['string'], - 'MongoDB\Driver\ReadConcern::unserialize' => ['void', 'serialized'=>'string'], - 'MongoDB\Driver\ReadPreference::__construct' => ['void', 'mode'=>'string|int', 'tagSets='=>'array', 'options='=>'array'], - 'MongoDB\Driver\ReadPreference::bsonSerialize' => ['object'], - 'MongoDB\Driver\ReadPreference::getHedge' => ['object|null'], + 'MongoDB\Driver\ReadConcern::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\Driver\ReadPreference::__construct' => ['void', 'mode' => 'string|int', 'tagSets=' => '?array', 'options=' => '?array'], + 'MongoDB\Driver\ReadPreference::getHedge' => ['?object'], 'MongoDB\Driver\ReadPreference::getMaxStalenessSeconds' => ['int'], 'MongoDB\Driver\ReadPreference::getMode' => ['int'], 'MongoDB\Driver\ReadPreference::getModeString' => ['string'], 'MongoDB\Driver\ReadPreference::getTagSets' => ['array'], + 'MongoDB\Driver\ReadPreference::bsonSerialize' => ['object|array'], 'MongoDB\Driver\ReadPreference::serialize' => ['string'], - 'MongoDB\Driver\ReadPreference::unserialize' => ['void', 'serialized'=>'string'], - 'MongoDB\Driver\Server::__construct' => ['void', 'host'=>'string', 'port'=>'string', 'options='=>'array', 'driverOptions='=>'array'], - 'MongoDB\Driver\Server::executeBulkWrite' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'zwrite'=>'BulkWrite'], - 'MongoDB\Driver\Server::executeCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command'], - 'MongoDB\Driver\Server::executeQuery' => ['MongoDB\Driver\Cursor', 'namespace'=>'string', 'zquery'=>'Query'], + 'MongoDB\Driver\ReadPreference::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\Driver\Server::executeBulkWrite' => ['MongoDB\Driver\WriteResult', 'namespace' => 'string', 'bulkWrite' => 'MongoDB\Driver\BulkWrite', 'options=' => 'MongoDB\Driver\WriteConcern|array|null'], + 'MongoDB\Driver\Server::executeCommand' => ['MongoDB\Driver\Cursor', 'db' => 'string', 'command' => 'MongoDB\Driver\Command', 'options=' => 'MongoDB\Driver\ReadPreference|array|null'], + 'MongoDB\Driver\Server::executeQuery' => ['MongoDB\Driver\Cursor', 'namespace' => 'string', 'query' => 'MongoDB\Driver\Query', 'options=' => 'MongoDB\Driver\ReadPreference|array|null'], + 'MongoDB\Driver\Server::executeReadCommand' => ['MongoDB\Driver\Cursor', 'db' => 'string', 'command' => 'MongoDB\Driver\Command', 'options=' => '?array'], + 'MongoDB\Driver\Server::executeReadWriteCommand' => ['MongoDB\Driver\Cursor', 'db' => 'string', 'command' => 'MongoDB\Driver\Command', 'options=' => '?array'], + 'MongoDB\Driver\Server::executeWriteCommand' => ['MongoDB\Driver\Cursor', 'db' => 'string', 'command' => 'MongoDB\Driver\Command', 'options=' => '?array'], 'MongoDB\Driver\Server::getHost' => ['string'], 'MongoDB\Driver\Server::getInfo' => ['array'], - 'MongoDB\Driver\Server::getLatency' => ['int'], + 'MongoDB\Driver\Server::getLatency' => ['?int'], 'MongoDB\Driver\Server::getPort' => ['int'], - 'MongoDB\Driver\Server::getState' => [''], + 'MongoDB\Driver\Server::getServerDescription' => ['MongoDB\Driver\ServerDescription'], 'MongoDB\Driver\Server::getTags' => ['array'], 'MongoDB\Driver\Server::getType' => ['int'], 'MongoDB\Driver\Server::isArbiter' => ['bool'], - 'MongoDB\Driver\Server::isDelayed' => [''], 'MongoDB\Driver\Server::isHidden' => ['bool'], 'MongoDB\Driver\Server::isPassive' => ['bool'], 'MongoDB\Driver\Server::isPrimary' => ['bool'], 'MongoDB\Driver\Server::isSecondary' => ['bool'], - 'MongoDB\Driver\WriteConcern::__construct' => ['void', 'wstring'=>'string|int', 'wtimeout='=>'int', 'journal='=>'bool'], - 'MongoDB\Driver\WriteConcern::bsonSerialize' => ['object'], + 'MongoDB\Driver\ServerApi::__construct' => ['void', 'version' => 'string', 'strict=' => '?bool', 'deprecationErrors=' => '?bool'], + 'MongoDB\Driver\ServerApi::bsonSerialize' => ['object|array'], + 'MongoDB\Driver\ServerApi::serialize' => ['string'], + 'MongoDB\Driver\ServerApi::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\Driver\ServerDescription::getHelloResponse' => ['array'], + 'MongoDB\Driver\ServerDescription::getHost' => ['string'], + 'MongoDB\Driver\ServerDescription::getLastUpdateTime' => ['int'], + 'MongoDB\Driver\ServerDescription::getPort' => ['int'], + 'MongoDB\Driver\ServerDescription::getRoundTripTime' => ['?int'], + 'MongoDB\Driver\ServerDescription::getType' => ['string'], + 'MongoDB\Driver\Session::abortTransaction' => ['void'], + 'MongoDB\Driver\Session::advanceClusterTime' => ['void', 'clusterTime' => 'object|array'], + 'MongoDB\Driver\Session::advanceOperationTime' => ['void', 'operationTime' => 'MongoDB\BSON\TimestampInterface'], + 'MongoDB\Driver\Session::commitTransaction' => ['void'], + 'MongoDB\Driver\Session::endSession' => ['void'], + 'MongoDB\Driver\Session::getClusterTime' => ['?object'], + 'MongoDB\Driver\Session::getLogicalSessionId' => ['object'], + 'MongoDB\Driver\Session::getOperationTime' => ['?MongoDB\BSON\Timestamp'], + 'MongoDB\Driver\Session::getServer' => ['?MongoDB\Driver\Server'], + 'MongoDB\Driver\Session::getTransactionOptions' => ['?array'], + 'MongoDB\Driver\Session::getTransactionState' => ['string'], + 'MongoDB\Driver\Session::isDirty' => ['bool'], + 'MongoDB\Driver\Session::isInTransaction' => ['bool'], + 'MongoDB\Driver\Session::startTransaction' => ['void', 'options=' => '?array'], + 'MongoDB\Driver\TopologyDescription::getServers' => ['array'], + 'MongoDB\Driver\TopologyDescription::getType' => ['string'], + 'MongoDB\Driver\TopologyDescription::hasReadableServer' => ['bool', 'readPreference=' => '?MongoDB\Driver\ReadPreference'], + 'MongoDB\Driver\TopologyDescription::hasWritableServer' => ['bool'], + 'MongoDB\Driver\WriteConcern::__construct' => ['void', 'w' => 'string|int', 'wtimeout=' => '?int', 'journal=' => '?bool'], 'MongoDB\Driver\WriteConcern::getJournal' => ['?bool'], - 'MongoDB\Driver\WriteConcern::getJurnal' => ['?bool'], - 'MongoDB\Driver\WriteConcern::getW' => ['int|null|string'], + 'MongoDB\Driver\WriteConcern::getW' => ['string|int|null'], 'MongoDB\Driver\WriteConcern::getWtimeout' => ['int'], 'MongoDB\Driver\WriteConcern::isDefault' => ['bool'], + 'MongoDB\Driver\WriteConcern::bsonSerialize' => ['object|array'], 'MongoDB\Driver\WriteConcern::serialize' => ['string'], - 'MongoDB\Driver\WriteConcern::unserialize' => ['void', 'serialized'=>'string'], + 'MongoDB\Driver\WriteConcern::unserialize' => ['void', 'serialized' => ''], 'MongoDB\Driver\WriteConcernError::getCode' => ['int'], - 'MongoDB\Driver\WriteConcernError::getInfo' => ['mixed'], + 'MongoDB\Driver\WriteConcernError::getInfo' => ['?object'], 'MongoDB\Driver\WriteConcernError::getMessage' => ['string'], 'MongoDB\Driver\WriteError::getCode' => ['int'], 'MongoDB\Driver\WriteError::getIndex' => ['int'], - 'MongoDB\Driver\WriteError::getInfo' => ['mixed'], + 'MongoDB\Driver\WriteError::getInfo' => ['?object'], 'MongoDB\Driver\WriteError::getMessage' => ['string'], - 'MongoDB\Driver\WriteException::getWriteResult' => [''], - 'MongoDB\Driver\WriteResult::getDeletedCount' => ['?int'], - 'MongoDB\Driver\WriteResult::getInfo' => [''], 'MongoDB\Driver\WriteResult::getInsertedCount' => ['?int'], 'MongoDB\Driver\WriteResult::getMatchedCount' => ['?int'], 'MongoDB\Driver\WriteResult::getModifiedCount' => ['?int'], - 'MongoDB\Driver\WriteResult::getServer' => ['MongoDB\Driver\Server'], + 'MongoDB\Driver\WriteResult::getDeletedCount' => ['?int'], 'MongoDB\Driver\WriteResult::getUpsertedCount' => ['?int'], + 'MongoDB\Driver\WriteResult::getServer' => ['MongoDB\Driver\Server'], 'MongoDB\Driver\WriteResult::getUpsertedIds' => ['array'], - 'MongoDB\Driver\WriteResult::getWriteConcernError' => ['MongoDB\Driver\WriteConcernError|null'], - 'MongoDB\Driver\WriteResult::getWriteErrors' => ['MongoDB\Driver\WriteError[]'], + 'MongoDB\Driver\WriteResult::getWriteConcernError' => ['?MongoDB\Driver\WriteConcernError'], + 'MongoDB\Driver\WriteResult::getWriteErrors' => ['array'], 'MongoDB\Driver\WriteResult::isAcknowledged' => ['bool'], 'MongoDate::__construct' => ['void', 'second='=>'int', 'usecond='=>'int'], 'MongoDate::__toString' => ['string'], @@ -13002,49 +13108,6 @@ 'mkdir' => ['bool', 'directory'=>'string', 'permissions='=>'int', 'recursive='=>'bool', 'context='=>'resource'], 'mktime' => ['int|false', 'hour='=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], 'money_format' => ['string', 'format'=>'string', 'value'=>'float'], - 'mongodb\driver\exception\commandexception::getResultDocument' => ['object'], - 'mongodb\driver\exception\runtimeexception::hasErrorLabel' => ['bool', 'errorLabel'=>'string'], - 'mongodb\driver\manager::executeReadCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'array'], - 'mongodb\driver\manager::executeReadWriteCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'array'], - 'mongodb\driver\manager::executeWriteCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'array'], - 'mongodb\driver\manager::startSession' => ['MongoDB\Driver\Session', 'options='=>'array'], - 'mongodb\driver\monitoring\commandfailedevent::getCommandName' => ['string'], - 'mongodb\driver\monitoring\commandfailedevent::getDurationMicros' => ['int'], - 'mongodb\driver\monitoring\commandfailedevent::getError' => ['Exception'], - 'mongodb\driver\monitoring\commandfailedevent::getOperationId' => ['string'], - 'mongodb\driver\monitoring\commandfailedevent::getReply' => ['object'], - 'mongodb\driver\monitoring\commandfailedevent::getRequestId' => ['string'], - 'mongodb\driver\monitoring\commandfailedevent::getServer' => ['MongoDB\Driver\Server'], - 'mongodb\driver\monitoring\commandstartedevent::getCommand' => ['object'], - 'mongodb\driver\monitoring\commandstartedevent::getCommandName' => ['string'], - 'mongodb\driver\monitoring\commandstartedevent::getDatabaseName' => ['string'], - 'mongodb\driver\monitoring\commandstartedevent::getOperationId' => ['string'], - 'mongodb\driver\monitoring\commandstartedevent::getRequestId' => ['string'], - 'mongodb\driver\monitoring\commandstartedevent::getServer' => ['MongoDB\Driver\Server'], - 'mongodb\driver\monitoring\commandsubscriber::commandFailed' => ['void', 'event'=>'MongoDB\Driver\Monitoring\CommandFailedEvent'], - 'mongodb\driver\monitoring\commandsubscriber::commandStarted' => ['void', 'event'=>'MongoDB\Driver\Monitoring\CommandStartedEvent'], - 'mongodb\driver\monitoring\commandsubscriber::commandSucceeded' => ['void', 'event'=>'MongoDB\Driver\Monitoring\CommandSucceededEvent'], - 'mongodb\driver\monitoring\commandsucceededevent::getCommandName' => ['string'], - 'mongodb\driver\monitoring\commandsucceededevent::getDurationMicros' => ['int'], - 'mongodb\driver\monitoring\commandsucceededevent::getOperationId' => ['string'], - 'mongodb\driver\monitoring\commandsucceededevent::getReply' => ['object'], - 'mongodb\driver\monitoring\commandsucceededevent::getRequestId' => ['string'], - 'mongodb\driver\monitoring\commandsucceededevent::getServer' => ['MongoDB\Driver\Server'], - 'mongodb\driver\server::executeReadCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'array'], - 'mongodb\driver\server::executeReadWriteCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'array'], - 'mongodb\driver\server::executeWriteCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'array'], - 'mongodb\driver\session::__construct' => ['void'], - 'mongodb\driver\session::abortTransaction' => ['void'], - 'mongodb\driver\session::advanceClusterTime' => ['void', 'clusterTime'=>'array|object'], - 'mongodb\driver\session::advanceOperationTime' => ['void', 'operationTime'=>'MongoDB\BSON\TimestampInterface'], - 'mongodb\driver\session::commitTransaction' => ['void'], - 'mongodb\driver\session::endSession' => ['void'], - 'mongodb\driver\session::getClusterTime' => ['?object'], - 'mongodb\driver\session::getLogicalSessionId' => ['object'], - 'mongodb\driver\session::getOperationTime' => ['MongoDB\BSON\Timestamp|null'], - 'mongodb\driver\session::getTransactionOptions' => ['array|null'], - 'mongodb\driver\session::getTransactionState' => ['string'], - 'mongodb\driver\session::startTransaction' => ['void', 'options'=>'array|object'], 'monitor_custom_event' => ['void', 'class'=>'string', 'text'=>'string', 'severe='=>'int', 'user_data='=>'mixed'], 'monitor_httperror_event' => ['void', 'error_code'=>'int', 'url'=>'string', 'severe='=>'int'], 'monitor_license_info' => ['array'], From 5b8f611e7318ceddbd0c381b4bacd03728da59cb Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 25 Aug 2022 15:27:04 +0200 Subject: [PATCH 073/178] Handle tentative return types for interfaces correctly --- dictionaries/CallMap.php | 47 +++++++++++++---------------- dictionaries/CallMap_historical.php | 47 +++++++++++++---------------- 2 files changed, 42 insertions(+), 52 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 1f6ca718260..f8dfb0dccc8 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -7700,8 +7700,8 @@ 'MongoDB\BSON\Binary::serialize' => ['string'], 'MongoDB\BSON\Binary::unserialize' => ['void', 'serialized' => ''], 'MongoDB\BSON\Binary::jsonSerialize' => ['mixed'], -'MongoDB\BSON\BinaryInterface::getData' => ['void'], -'MongoDB\BSON\BinaryInterface::getType' => ['void'], +'MongoDB\BSON\BinaryInterface::getData' => ['string'], +'MongoDB\BSON\BinaryInterface::getType' => ['int'], 'MongoDB\BSON\BinaryInterface::__toString' => ['string'], 'MongoDB\BSON\DBPointer::__toString' => ['string'], 'MongoDB\BSON\DBPointer::serialize' => ['string'], @@ -7712,7 +7712,7 @@ 'MongoDB\BSON\Decimal128::serialize' => ['string'], 'MongoDB\BSON\Decimal128::unserialize' => ['void', 'serialized' => ''], 'MongoDB\BSON\Decimal128::jsonSerialize' => ['mixed'], -'MongoDB\BSON\Decimal128Interface::__toString' => ['void'], +'MongoDB\BSON\Decimal128Interface::__toString' => ['string'], 'MongoDB\BSON\Int64::__toString' => ['string'], 'MongoDB\BSON\Int64::serialize' => ['string'], 'MongoDB\BSON\Int64::unserialize' => ['void', 'serialized' => ''], @@ -7724,9 +7724,9 @@ 'MongoDB\BSON\Javascript::serialize' => ['string'], 'MongoDB\BSON\Javascript::unserialize' => ['void', 'serialized' => ''], 'MongoDB\BSON\Javascript::jsonSerialize' => ['mixed'], -'MongoDB\BSON\JavascriptInterface::getCode' => ['void'], -'MongoDB\BSON\JavascriptInterface::getScope' => ['void'], -'MongoDB\BSON\JavascriptInterface::__toString' => ['void'], +'MongoDB\BSON\JavascriptInterface::getCode' => ['string'], +'MongoDB\BSON\JavascriptInterface::getScope' => ['?object'], +'MongoDB\BSON\JavascriptInterface::__toString' => ['string'], 'MongoDB\BSON\MaxKey::serialize' => ['string'], 'MongoDB\BSON\MaxKey::unserialize' => ['void', 'serialized' => ''], 'MongoDB\BSON\MaxKey::jsonSerialize' => ['mixed'], @@ -7739,8 +7739,8 @@ 'MongoDB\BSON\ObjectId::serialize' => ['string'], 'MongoDB\BSON\ObjectId::unserialize' => ['void', 'serialized' => ''], 'MongoDB\BSON\ObjectId::jsonSerialize' => ['mixed'], -'MongoDB\BSON\ObjectIdInterface::getTimestamp' => ['void'], -'MongoDB\BSON\ObjectIdInterface::__toString' => ['void'], +'MongoDB\BSON\ObjectIdInterface::getTimestamp' => ['int'], +'MongoDB\BSON\ObjectIdInterface::__toString' => ['string'], 'MongoDB\BSON\Regex::__construct' => ['void', 'pattern' => 'string', 'flags=' => 'string'], 'MongoDB\BSON\Regex::getPattern' => ['string'], 'MongoDB\BSON\Regex::getFlags' => ['string'], @@ -7748,10 +7748,10 @@ 'MongoDB\BSON\Regex::serialize' => ['string'], 'MongoDB\BSON\Regex::unserialize' => ['void', 'serialized' => ''], 'MongoDB\BSON\Regex::jsonSerialize' => ['mixed'], -'MongoDB\BSON\RegexInterface::getPattern' => ['void'], -'MongoDB\BSON\RegexInterface::getFlags' => ['void'], -'MongoDB\BSON\RegexInterface::__toString' => ['void'], -'MongoDB\BSON\Serializable::bsonSerialize' => ['void'], +'MongoDB\BSON\RegexInterface::getPattern' => ['string'], +'MongoDB\BSON\RegexInterface::getFlags' => ['string'], +'MongoDB\BSON\RegexInterface::__toString' => ['string'], +'MongoDB\BSON\Serializable::bsonSerialize' => ['object|array'], 'MongoDB\BSON\Symbol::__toString' => ['string'], 'MongoDB\BSON\Symbol::serialize' => ['string'], 'MongoDB\BSON\Symbol::unserialize' => ['void', 'serialized' => ''], @@ -7763,17 +7763,17 @@ 'MongoDB\BSON\Timestamp::serialize' => ['string'], 'MongoDB\BSON\Timestamp::unserialize' => ['void', 'serialized' => ''], 'MongoDB\BSON\Timestamp::jsonSerialize' => ['mixed'], -'MongoDB\BSON\TimestampInterface::getTimestamp' => ['void'], -'MongoDB\BSON\TimestampInterface::getIncrement' => ['void'], -'MongoDB\BSON\TimestampInterface::__toString' => ['void'], +'MongoDB\BSON\TimestampInterface::getTimestamp' => ['int'], +'MongoDB\BSON\TimestampInterface::getIncrement' => ['int'], +'MongoDB\BSON\TimestampInterface::__toString' => ['string'], 'MongoDB\BSON\UTCDateTime::__construct' => ['void', 'milliseconds=' => 'DateTimeInterface|string|int|float|null'], 'MongoDB\BSON\UTCDateTime::toDateTime' => ['DateTime'], 'MongoDB\BSON\UTCDateTime::__toString' => ['string'], 'MongoDB\BSON\UTCDateTime::serialize' => ['string'], 'MongoDB\BSON\UTCDateTime::unserialize' => ['void', 'serialized' => ''], 'MongoDB\BSON\UTCDateTime::jsonSerialize' => ['mixed'], -'MongoDB\BSON\UTCDateTimeInterface::toDateTime' => ['void'], -'MongoDB\BSON\UTCDateTimeInterface::__toString' => ['void'], +'MongoDB\BSON\UTCDateTimeInterface::toDateTime' => ['DateTime'], +'MongoDB\BSON\UTCDateTimeInterface::__toString' => ['string'], 'MongoDB\BSON\Undefined::__toString' => ['string'], 'MongoDB\BSON\Undefined::serialize' => ['string'], 'MongoDB\BSON\Undefined::unserialize' => ['void', 'serialized' => ''], @@ -7809,16 +7809,11 @@ 'MongoDB\Driver\CursorId::__toString' => ['string'], 'MongoDB\Driver\CursorId::serialize' => ['string'], 'MongoDB\Driver\CursorId::unserialize' => ['void', 'serialized' => ''], -'MongoDB\Driver\CursorInterface::current' => ['object|array|null'], -'MongoDB\Driver\CursorInterface::getId' => ['void'], -'MongoDB\Driver\CursorInterface::getServer' => ['void'], -'MongoDB\Driver\CursorInterface::isDead' => ['void'], -'MongoDB\Driver\CursorInterface::key' => ['?int'], -'MongoDB\Driver\CursorInterface::next' => ['void'], -'MongoDB\Driver\CursorInterface::rewind' => ['void'], +'MongoDB\Driver\CursorInterface::getId' => ['MongoDB\Driver\CursorId'], +'MongoDB\Driver\CursorInterface::getServer' => ['MongoDB\Driver\Server'], +'MongoDB\Driver\CursorInterface::isDead' => ['bool'], 'MongoDB\Driver\CursorInterface::setTypeMap' => ['void', 'typemap' => 'array'], -'MongoDB\Driver\CursorInterface::toArray' => ['void'], -'MongoDB\Driver\CursorInterface::valid' => ['bool'], +'MongoDB\Driver\CursorInterface::toArray' => ['array'], 'MongoDB\Driver\Exception\AuthenticationException::__toString' => ['string'], 'MongoDB\Driver\Exception\BulkWriteException::__toString' => ['string'], 'MongoDB\Driver\Exception\CommandException::getResultDocument' => ['object'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index f2bf1d49284..74057a56163 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -3972,8 +3972,8 @@ 'MongoDB\BSON\Binary::serialize' => ['string'], 'MongoDB\BSON\Binary::unserialize' => ['void', 'serialized' => ''], 'MongoDB\BSON\Binary::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\BinaryInterface::getData' => ['void'], - 'MongoDB\BSON\BinaryInterface::getType' => ['void'], + 'MongoDB\BSON\BinaryInterface::getData' => ['string'], + 'MongoDB\BSON\BinaryInterface::getType' => ['int'], 'MongoDB\BSON\BinaryInterface::__toString' => ['string'], 'MongoDB\BSON\DBPointer::__toString' => ['string'], 'MongoDB\BSON\DBPointer::serialize' => ['string'], @@ -3984,7 +3984,7 @@ 'MongoDB\BSON\Decimal128::serialize' => ['string'], 'MongoDB\BSON\Decimal128::unserialize' => ['void', 'serialized' => ''], 'MongoDB\BSON\Decimal128::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\Decimal128Interface::__toString' => ['void'], + 'MongoDB\BSON\Decimal128Interface::__toString' => ['string'], 'MongoDB\BSON\Int64::__toString' => ['string'], 'MongoDB\BSON\Int64::serialize' => ['string'], 'MongoDB\BSON\Int64::unserialize' => ['void', 'serialized' => ''], @@ -3996,9 +3996,9 @@ 'MongoDB\BSON\Javascript::serialize' => ['string'], 'MongoDB\BSON\Javascript::unserialize' => ['void', 'serialized' => ''], 'MongoDB\BSON\Javascript::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\JavascriptInterface::getCode' => ['void'], - 'MongoDB\BSON\JavascriptInterface::getScope' => ['void'], - 'MongoDB\BSON\JavascriptInterface::__toString' => ['void'], + 'MongoDB\BSON\JavascriptInterface::getCode' => ['string'], + 'MongoDB\BSON\JavascriptInterface::getScope' => ['?object'], + 'MongoDB\BSON\JavascriptInterface::__toString' => ['string'], 'MongoDB\BSON\MaxKey::serialize' => ['string'], 'MongoDB\BSON\MaxKey::unserialize' => ['void', 'serialized' => ''], 'MongoDB\BSON\MaxKey::jsonSerialize' => ['mixed'], @@ -4011,8 +4011,8 @@ 'MongoDB\BSON\ObjectId::serialize' => ['string'], 'MongoDB\BSON\ObjectId::unserialize' => ['void', 'serialized' => ''], 'MongoDB\BSON\ObjectId::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\ObjectIdInterface::getTimestamp' => ['void'], - 'MongoDB\BSON\ObjectIdInterface::__toString' => ['void'], + 'MongoDB\BSON\ObjectIdInterface::getTimestamp' => ['int'], + 'MongoDB\BSON\ObjectIdInterface::__toString' => ['string'], 'MongoDB\BSON\Regex::__construct' => ['void', 'pattern' => 'string', 'flags=' => 'string'], 'MongoDB\BSON\Regex::getPattern' => ['string'], 'MongoDB\BSON\Regex::getFlags' => ['string'], @@ -4020,10 +4020,10 @@ 'MongoDB\BSON\Regex::serialize' => ['string'], 'MongoDB\BSON\Regex::unserialize' => ['void', 'serialized' => ''], 'MongoDB\BSON\Regex::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\RegexInterface::getPattern' => ['void'], - 'MongoDB\BSON\RegexInterface::getFlags' => ['void'], - 'MongoDB\BSON\RegexInterface::__toString' => ['void'], - 'MongoDB\BSON\Serializable::bsonSerialize' => ['void'], + 'MongoDB\BSON\RegexInterface::getPattern' => ['string'], + 'MongoDB\BSON\RegexInterface::getFlags' => ['string'], + 'MongoDB\BSON\RegexInterface::__toString' => ['string'], + 'MongoDB\BSON\Serializable::bsonSerialize' => ['object|array'], 'MongoDB\BSON\Symbol::__toString' => ['string'], 'MongoDB\BSON\Symbol::serialize' => ['string'], 'MongoDB\BSON\Symbol::unserialize' => ['void', 'serialized' => ''], @@ -4035,17 +4035,17 @@ 'MongoDB\BSON\Timestamp::serialize' => ['string'], 'MongoDB\BSON\Timestamp::unserialize' => ['void', 'serialized' => ''], 'MongoDB\BSON\Timestamp::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\TimestampInterface::getTimestamp' => ['void'], - 'MongoDB\BSON\TimestampInterface::getIncrement' => ['void'], - 'MongoDB\BSON\TimestampInterface::__toString' => ['void'], + 'MongoDB\BSON\TimestampInterface::getTimestamp' => ['int'], + 'MongoDB\BSON\TimestampInterface::getIncrement' => ['int'], + 'MongoDB\BSON\TimestampInterface::__toString' => ['string'], 'MongoDB\BSON\UTCDateTime::__construct' => ['void', 'milliseconds=' => 'DateTimeInterface|string|int|float|null'], 'MongoDB\BSON\UTCDateTime::toDateTime' => ['DateTime'], 'MongoDB\BSON\UTCDateTime::__toString' => ['string'], 'MongoDB\BSON\UTCDateTime::serialize' => ['string'], 'MongoDB\BSON\UTCDateTime::unserialize' => ['void', 'serialized' => ''], 'MongoDB\BSON\UTCDateTime::jsonSerialize' => ['mixed'], - 'MongoDB\BSON\UTCDateTimeInterface::toDateTime' => ['void'], - 'MongoDB\BSON\UTCDateTimeInterface::__toString' => ['void'], + 'MongoDB\BSON\UTCDateTimeInterface::toDateTime' => ['DateTime'], + 'MongoDB\BSON\UTCDateTimeInterface::__toString' => ['string'], 'MongoDB\BSON\Undefined::__toString' => ['string'], 'MongoDB\BSON\Undefined::serialize' => ['string'], 'MongoDB\BSON\Undefined::unserialize' => ['void', 'serialized' => ''], @@ -4081,16 +4081,11 @@ 'MongoDB\Driver\CursorId::__toString' => ['string'], 'MongoDB\Driver\CursorId::serialize' => ['string'], 'MongoDB\Driver\CursorId::unserialize' => ['void', 'serialized' => ''], - 'MongoDB\Driver\CursorInterface::current' => ['object|array|null'], - 'MongoDB\Driver\CursorInterface::getId' => ['void'], - 'MongoDB\Driver\CursorInterface::getServer' => ['void'], - 'MongoDB\Driver\CursorInterface::isDead' => ['void'], - 'MongoDB\Driver\CursorInterface::key' => ['?int'], - 'MongoDB\Driver\CursorInterface::next' => ['void'], - 'MongoDB\Driver\CursorInterface::rewind' => ['void'], + 'MongoDB\Driver\CursorInterface::getId' => ['MongoDB\Driver\CursorId'], + 'MongoDB\Driver\CursorInterface::getServer' => ['MongoDB\Driver\Server'], + 'MongoDB\Driver\CursorInterface::isDead' => ['bool'], 'MongoDB\Driver\CursorInterface::setTypeMap' => ['void', 'typemap' => 'array'], - 'MongoDB\Driver\CursorInterface::toArray' => ['void'], - 'MongoDB\Driver\CursorInterface::valid' => ['bool'], + 'MongoDB\Driver\CursorInterface::toArray' => ['array'], 'MongoDB\Driver\Exception\AuthenticationException::__toString' => ['string'], 'MongoDB\Driver\Exception\BulkWriteException::__toString' => ['string'], 'MongoDB\Driver\Exception\CommandException::getResultDocument' => ['object'], From 48bf5496d8c2678cbbd24129837b41e2678c36ff Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Fri, 26 Aug 2022 10:03:08 +0200 Subject: [PATCH 074/178] Add missing parameter type for Serializable::unserialize --- dictionaries/CallMap.php | 36 ++++++++++++++--------------- dictionaries/CallMap_historical.php | 36 ++++++++++++++--------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index f8dfb0dccc8..70655310d70 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -7698,46 +7698,46 @@ 'MongoDB\BSON\Binary::getType' => ['int'], 'MongoDB\BSON\Binary::__toString' => ['string'], 'MongoDB\BSON\Binary::serialize' => ['string'], -'MongoDB\BSON\Binary::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\Binary::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\Binary::jsonSerialize' => ['mixed'], 'MongoDB\BSON\BinaryInterface::getData' => ['string'], 'MongoDB\BSON\BinaryInterface::getType' => ['int'], 'MongoDB\BSON\BinaryInterface::__toString' => ['string'], 'MongoDB\BSON\DBPointer::__toString' => ['string'], 'MongoDB\BSON\DBPointer::serialize' => ['string'], -'MongoDB\BSON\DBPointer::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\DBPointer::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\DBPointer::jsonSerialize' => ['mixed'], 'MongoDB\BSON\Decimal128::__construct' => ['void', 'value' => 'string'], 'MongoDB\BSON\Decimal128::__toString' => ['string'], 'MongoDB\BSON\Decimal128::serialize' => ['string'], -'MongoDB\BSON\Decimal128::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\Decimal128::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\Decimal128::jsonSerialize' => ['mixed'], 'MongoDB\BSON\Decimal128Interface::__toString' => ['string'], 'MongoDB\BSON\Int64::__toString' => ['string'], 'MongoDB\BSON\Int64::serialize' => ['string'], -'MongoDB\BSON\Int64::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\Int64::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\Int64::jsonSerialize' => ['mixed'], 'MongoDB\BSON\Javascript::__construct' => ['void', 'code' => 'string', 'scope=' => 'object|array|null'], 'MongoDB\BSON\Javascript::getCode' => ['string'], 'MongoDB\BSON\Javascript::getScope' => ['?object'], 'MongoDB\BSON\Javascript::__toString' => ['string'], 'MongoDB\BSON\Javascript::serialize' => ['string'], -'MongoDB\BSON\Javascript::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\Javascript::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\Javascript::jsonSerialize' => ['mixed'], 'MongoDB\BSON\JavascriptInterface::getCode' => ['string'], 'MongoDB\BSON\JavascriptInterface::getScope' => ['?object'], 'MongoDB\BSON\JavascriptInterface::__toString' => ['string'], 'MongoDB\BSON\MaxKey::serialize' => ['string'], -'MongoDB\BSON\MaxKey::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\MaxKey::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\MaxKey::jsonSerialize' => ['mixed'], 'MongoDB\BSON\MinKey::serialize' => ['string'], -'MongoDB\BSON\MinKey::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\MinKey::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\MinKey::jsonSerialize' => ['mixed'], 'MongoDB\BSON\ObjectId::__construct' => ['void', 'id=' => '?string'], 'MongoDB\BSON\ObjectId::getTimestamp' => ['int'], 'MongoDB\BSON\ObjectId::__toString' => ['string'], 'MongoDB\BSON\ObjectId::serialize' => ['string'], -'MongoDB\BSON\ObjectId::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\ObjectId::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\ObjectId::jsonSerialize' => ['mixed'], 'MongoDB\BSON\ObjectIdInterface::getTimestamp' => ['int'], 'MongoDB\BSON\ObjectIdInterface::__toString' => ['string'], @@ -7746,7 +7746,7 @@ 'MongoDB\BSON\Regex::getFlags' => ['string'], 'MongoDB\BSON\Regex::__toString' => ['string'], 'MongoDB\BSON\Regex::serialize' => ['string'], -'MongoDB\BSON\Regex::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\Regex::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\Regex::jsonSerialize' => ['mixed'], 'MongoDB\BSON\RegexInterface::getPattern' => ['string'], 'MongoDB\BSON\RegexInterface::getFlags' => ['string'], @@ -7754,14 +7754,14 @@ 'MongoDB\BSON\Serializable::bsonSerialize' => ['object|array'], 'MongoDB\BSON\Symbol::__toString' => ['string'], 'MongoDB\BSON\Symbol::serialize' => ['string'], -'MongoDB\BSON\Symbol::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\Symbol::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\Symbol::jsonSerialize' => ['mixed'], 'MongoDB\BSON\Timestamp::__construct' => ['void', 'increment' => 'string|int', 'timestamp' => 'string|int'], 'MongoDB\BSON\Timestamp::getTimestamp' => ['int'], 'MongoDB\BSON\Timestamp::getIncrement' => ['int'], 'MongoDB\BSON\Timestamp::__toString' => ['string'], 'MongoDB\BSON\Timestamp::serialize' => ['string'], -'MongoDB\BSON\Timestamp::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\Timestamp::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\Timestamp::jsonSerialize' => ['mixed'], 'MongoDB\BSON\TimestampInterface::getTimestamp' => ['int'], 'MongoDB\BSON\TimestampInterface::getIncrement' => ['int'], @@ -7770,13 +7770,13 @@ 'MongoDB\BSON\UTCDateTime::toDateTime' => ['DateTime'], 'MongoDB\BSON\UTCDateTime::__toString' => ['string'], 'MongoDB\BSON\UTCDateTime::serialize' => ['string'], -'MongoDB\BSON\UTCDateTime::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\UTCDateTime::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\UTCDateTime::jsonSerialize' => ['mixed'], 'MongoDB\BSON\UTCDateTimeInterface::toDateTime' => ['DateTime'], 'MongoDB\BSON\UTCDateTimeInterface::__toString' => ['string'], 'MongoDB\BSON\Undefined::__toString' => ['string'], 'MongoDB\BSON\Undefined::serialize' => ['string'], -'MongoDB\BSON\Undefined::unserialize' => ['void', 'serialized' => ''], +'MongoDB\BSON\Undefined::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\Undefined::jsonSerialize' => ['mixed'], 'MongoDB\BSON\Unserializable::bsonUnserialize' => ['void', 'data' => 'array'], 'MongoDB\Driver\BulkWrite::__construct' => ['void', 'options=' => '?array'], @@ -7808,7 +7808,7 @@ 'MongoDB\Driver\Cursor::valid' => ['bool'], 'MongoDB\Driver\CursorId::__toString' => ['string'], 'MongoDB\Driver\CursorId::serialize' => ['string'], -'MongoDB\Driver\CursorId::unserialize' => ['void', 'serialized' => ''], +'MongoDB\Driver\CursorId::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\Driver\CursorInterface::getId' => ['MongoDB\Driver\CursorId'], 'MongoDB\Driver\CursorInterface::getServer' => ['MongoDB\Driver\Server'], 'MongoDB\Driver\CursorInterface::isDead' => ['bool'], @@ -7921,7 +7921,7 @@ 'MongoDB\Driver\ReadConcern::isDefault' => ['bool'], 'MongoDB\Driver\ReadConcern::bsonSerialize' => ['object|array'], 'MongoDB\Driver\ReadConcern::serialize' => ['string'], -'MongoDB\Driver\ReadConcern::unserialize' => ['void', 'serialized' => ''], +'MongoDB\Driver\ReadConcern::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\Driver\ReadPreference::__construct' => ['void', 'mode' => 'string|int', 'tagSets=' => '?array', 'options=' => '?array'], 'MongoDB\Driver\ReadPreference::getHedge' => ['?object'], 'MongoDB\Driver\ReadPreference::getMaxStalenessSeconds' => ['int'], @@ -7930,7 +7930,7 @@ 'MongoDB\Driver\ReadPreference::getTagSets' => ['array'], 'MongoDB\Driver\ReadPreference::bsonSerialize' => ['object|array'], 'MongoDB\Driver\ReadPreference::serialize' => ['string'], -'MongoDB\Driver\ReadPreference::unserialize' => ['void', 'serialized' => ''], +'MongoDB\Driver\ReadPreference::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\Driver\Server::executeBulkWrite' => ['MongoDB\Driver\WriteResult', 'namespace' => 'string', 'bulkWrite' => 'MongoDB\Driver\BulkWrite', 'options=' => 'MongoDB\Driver\WriteConcern|array|null'], 'MongoDB\Driver\Server::executeCommand' => ['MongoDB\Driver\Cursor', 'db' => 'string', 'command' => 'MongoDB\Driver\Command', 'options=' => 'MongoDB\Driver\ReadPreference|array|null'], 'MongoDB\Driver\Server::executeQuery' => ['MongoDB\Driver\Cursor', 'namespace' => 'string', 'query' => 'MongoDB\Driver\Query', 'options=' => 'MongoDB\Driver\ReadPreference|array|null'], @@ -7952,7 +7952,7 @@ 'MongoDB\Driver\ServerApi::__construct' => ['void', 'version' => 'string', 'strict=' => '?bool', 'deprecationErrors=' => '?bool'], 'MongoDB\Driver\ServerApi::bsonSerialize' => ['object|array'], 'MongoDB\Driver\ServerApi::serialize' => ['string'], -'MongoDB\Driver\ServerApi::unserialize' => ['void', 'serialized' => ''], +'MongoDB\Driver\ServerApi::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\Driver\ServerDescription::getHelloResponse' => ['array'], 'MongoDB\Driver\ServerDescription::getHost' => ['string'], 'MongoDB\Driver\ServerDescription::getLastUpdateTime' => ['int'], @@ -7984,7 +7984,7 @@ 'MongoDB\Driver\WriteConcern::isDefault' => ['bool'], 'MongoDB\Driver\WriteConcern::bsonSerialize' => ['object|array'], 'MongoDB\Driver\WriteConcern::serialize' => ['string'], -'MongoDB\Driver\WriteConcern::unserialize' => ['void', 'serialized' => ''], +'MongoDB\Driver\WriteConcern::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\Driver\WriteConcernError::getCode' => ['int'], 'MongoDB\Driver\WriteConcernError::getInfo' => ['?object'], 'MongoDB\Driver\WriteConcernError::getMessage' => ['string'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 74057a56163..6f50add98a1 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -3970,46 +3970,46 @@ 'MongoDB\BSON\Binary::getType' => ['int'], 'MongoDB\BSON\Binary::__toString' => ['string'], 'MongoDB\BSON\Binary::serialize' => ['string'], - 'MongoDB\BSON\Binary::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\Binary::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\Binary::jsonSerialize' => ['mixed'], 'MongoDB\BSON\BinaryInterface::getData' => ['string'], 'MongoDB\BSON\BinaryInterface::getType' => ['int'], 'MongoDB\BSON\BinaryInterface::__toString' => ['string'], 'MongoDB\BSON\DBPointer::__toString' => ['string'], 'MongoDB\BSON\DBPointer::serialize' => ['string'], - 'MongoDB\BSON\DBPointer::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\DBPointer::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\DBPointer::jsonSerialize' => ['mixed'], 'MongoDB\BSON\Decimal128::__construct' => ['void', 'value' => 'string'], 'MongoDB\BSON\Decimal128::__toString' => ['string'], 'MongoDB\BSON\Decimal128::serialize' => ['string'], - 'MongoDB\BSON\Decimal128::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\Decimal128::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\Decimal128::jsonSerialize' => ['mixed'], 'MongoDB\BSON\Decimal128Interface::__toString' => ['string'], 'MongoDB\BSON\Int64::__toString' => ['string'], 'MongoDB\BSON\Int64::serialize' => ['string'], - 'MongoDB\BSON\Int64::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\Int64::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\Int64::jsonSerialize' => ['mixed'], 'MongoDB\BSON\Javascript::__construct' => ['void', 'code' => 'string', 'scope=' => 'object|array|null'], 'MongoDB\BSON\Javascript::getCode' => ['string'], 'MongoDB\BSON\Javascript::getScope' => ['?object'], 'MongoDB\BSON\Javascript::__toString' => ['string'], 'MongoDB\BSON\Javascript::serialize' => ['string'], - 'MongoDB\BSON\Javascript::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\Javascript::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\Javascript::jsonSerialize' => ['mixed'], 'MongoDB\BSON\JavascriptInterface::getCode' => ['string'], 'MongoDB\BSON\JavascriptInterface::getScope' => ['?object'], 'MongoDB\BSON\JavascriptInterface::__toString' => ['string'], 'MongoDB\BSON\MaxKey::serialize' => ['string'], - 'MongoDB\BSON\MaxKey::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\MaxKey::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\MaxKey::jsonSerialize' => ['mixed'], 'MongoDB\BSON\MinKey::serialize' => ['string'], - 'MongoDB\BSON\MinKey::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\MinKey::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\MinKey::jsonSerialize' => ['mixed'], 'MongoDB\BSON\ObjectId::__construct' => ['void', 'id=' => '?string'], 'MongoDB\BSON\ObjectId::getTimestamp' => ['int'], 'MongoDB\BSON\ObjectId::__toString' => ['string'], 'MongoDB\BSON\ObjectId::serialize' => ['string'], - 'MongoDB\BSON\ObjectId::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\ObjectId::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\ObjectId::jsonSerialize' => ['mixed'], 'MongoDB\BSON\ObjectIdInterface::getTimestamp' => ['int'], 'MongoDB\BSON\ObjectIdInterface::__toString' => ['string'], @@ -4018,7 +4018,7 @@ 'MongoDB\BSON\Regex::getFlags' => ['string'], 'MongoDB\BSON\Regex::__toString' => ['string'], 'MongoDB\BSON\Regex::serialize' => ['string'], - 'MongoDB\BSON\Regex::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\Regex::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\Regex::jsonSerialize' => ['mixed'], 'MongoDB\BSON\RegexInterface::getPattern' => ['string'], 'MongoDB\BSON\RegexInterface::getFlags' => ['string'], @@ -4026,14 +4026,14 @@ 'MongoDB\BSON\Serializable::bsonSerialize' => ['object|array'], 'MongoDB\BSON\Symbol::__toString' => ['string'], 'MongoDB\BSON\Symbol::serialize' => ['string'], - 'MongoDB\BSON\Symbol::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\Symbol::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\Symbol::jsonSerialize' => ['mixed'], 'MongoDB\BSON\Timestamp::__construct' => ['void', 'increment' => 'string|int', 'timestamp' => 'string|int'], 'MongoDB\BSON\Timestamp::getTimestamp' => ['int'], 'MongoDB\BSON\Timestamp::getIncrement' => ['int'], 'MongoDB\BSON\Timestamp::__toString' => ['string'], 'MongoDB\BSON\Timestamp::serialize' => ['string'], - 'MongoDB\BSON\Timestamp::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\Timestamp::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\Timestamp::jsonSerialize' => ['mixed'], 'MongoDB\BSON\TimestampInterface::getTimestamp' => ['int'], 'MongoDB\BSON\TimestampInterface::getIncrement' => ['int'], @@ -4042,13 +4042,13 @@ 'MongoDB\BSON\UTCDateTime::toDateTime' => ['DateTime'], 'MongoDB\BSON\UTCDateTime::__toString' => ['string'], 'MongoDB\BSON\UTCDateTime::serialize' => ['string'], - 'MongoDB\BSON\UTCDateTime::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\UTCDateTime::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\UTCDateTime::jsonSerialize' => ['mixed'], 'MongoDB\BSON\UTCDateTimeInterface::toDateTime' => ['DateTime'], 'MongoDB\BSON\UTCDateTimeInterface::__toString' => ['string'], 'MongoDB\BSON\Undefined::__toString' => ['string'], 'MongoDB\BSON\Undefined::serialize' => ['string'], - 'MongoDB\BSON\Undefined::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\BSON\Undefined::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\BSON\Undefined::jsonSerialize' => ['mixed'], 'MongoDB\BSON\Unserializable::bsonUnserialize' => ['void', 'data' => 'array'], 'MongoDB\Driver\BulkWrite::__construct' => ['void', 'options=' => '?array'], @@ -4080,7 +4080,7 @@ 'MongoDB\Driver\Cursor::valid' => ['bool'], 'MongoDB\Driver\CursorId::__toString' => ['string'], 'MongoDB\Driver\CursorId::serialize' => ['string'], - 'MongoDB\Driver\CursorId::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\Driver\CursorId::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\Driver\CursorInterface::getId' => ['MongoDB\Driver\CursorId'], 'MongoDB\Driver\CursorInterface::getServer' => ['MongoDB\Driver\Server'], 'MongoDB\Driver\CursorInterface::isDead' => ['bool'], @@ -4193,7 +4193,7 @@ 'MongoDB\Driver\ReadConcern::isDefault' => ['bool'], 'MongoDB\Driver\ReadConcern::bsonSerialize' => ['object|array'], 'MongoDB\Driver\ReadConcern::serialize' => ['string'], - 'MongoDB\Driver\ReadConcern::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\Driver\ReadConcern::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\Driver\ReadPreference::__construct' => ['void', 'mode' => 'string|int', 'tagSets=' => '?array', 'options=' => '?array'], 'MongoDB\Driver\ReadPreference::getHedge' => ['?object'], 'MongoDB\Driver\ReadPreference::getMaxStalenessSeconds' => ['int'], @@ -4202,7 +4202,7 @@ 'MongoDB\Driver\ReadPreference::getTagSets' => ['array'], 'MongoDB\Driver\ReadPreference::bsonSerialize' => ['object|array'], 'MongoDB\Driver\ReadPreference::serialize' => ['string'], - 'MongoDB\Driver\ReadPreference::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\Driver\ReadPreference::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\Driver\Server::executeBulkWrite' => ['MongoDB\Driver\WriteResult', 'namespace' => 'string', 'bulkWrite' => 'MongoDB\Driver\BulkWrite', 'options=' => 'MongoDB\Driver\WriteConcern|array|null'], 'MongoDB\Driver\Server::executeCommand' => ['MongoDB\Driver\Cursor', 'db' => 'string', 'command' => 'MongoDB\Driver\Command', 'options=' => 'MongoDB\Driver\ReadPreference|array|null'], 'MongoDB\Driver\Server::executeQuery' => ['MongoDB\Driver\Cursor', 'namespace' => 'string', 'query' => 'MongoDB\Driver\Query', 'options=' => 'MongoDB\Driver\ReadPreference|array|null'], @@ -4224,7 +4224,7 @@ 'MongoDB\Driver\ServerApi::__construct' => ['void', 'version' => 'string', 'strict=' => '?bool', 'deprecationErrors=' => '?bool'], 'MongoDB\Driver\ServerApi::bsonSerialize' => ['object|array'], 'MongoDB\Driver\ServerApi::serialize' => ['string'], - 'MongoDB\Driver\ServerApi::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\Driver\ServerApi::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\Driver\ServerDescription::getHelloResponse' => ['array'], 'MongoDB\Driver\ServerDescription::getHost' => ['string'], 'MongoDB\Driver\ServerDescription::getLastUpdateTime' => ['int'], @@ -4256,7 +4256,7 @@ 'MongoDB\Driver\WriteConcern::isDefault' => ['bool'], 'MongoDB\Driver\WriteConcern::bsonSerialize' => ['object|array'], 'MongoDB\Driver\WriteConcern::serialize' => ['string'], - 'MongoDB\Driver\WriteConcern::unserialize' => ['void', 'serialized' => ''], + 'MongoDB\Driver\WriteConcern::unserialize' => ['void', 'serialized' => 'string'], 'MongoDB\Driver\WriteConcernError::getCode' => ['int'], 'MongoDB\Driver\WriteConcernError::getInfo' => ['?object'], 'MongoDB\Driver\WriteConcernError::getMessage' => ['string'], From d7097281ba7b8bbabf7ea3fe038f85db7c6fd266 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhuravlev Date: Wed, 31 Aug 2022 21:02:20 +1200 Subject: [PATCH 075/178] trim(), ltrim(), rtrim() now keep lowercase string attribute --- stubs/CoreGenericFunctions.phpstub | 14 +++++++++++++- tests/FunctionCallTest.php | 27 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/stubs/CoreGenericFunctions.phpstub b/stubs/CoreGenericFunctions.phpstub index 720ba46e1d8..6cc5eec57a4 100644 --- a/stubs/CoreGenericFunctions.phpstub +++ b/stubs/CoreGenericFunctions.phpstub @@ -566,6 +566,12 @@ function strpos($haystack, $needle, int $offset = 0) : int {} /** * @psalm-pure * + * @return ( + * $string is class-string + * ? ($characters is '\\' ? class-string : string) + * : ($string is lowercase-string ? lowercase-string : string) + * ) + * * @psalm-flow ($string) -> return */ function trim(string $string, string $characters = " \t\n\r\0\x0B") : string {} @@ -573,7 +579,11 @@ function trim(string $string, string $characters = " \t\n\r\0\x0B") : string {} /** * @psalm-pure * - * @return ($string is class-string ? ($characters is '\\' ? class-string : string) : string) + * @return ( + * $string is class-string + * ? ($characters is '\\' ? class-string : string) + * : ($string is lowercase-string ? lowercase-string : string) + * ) * * @psalm-flow ($string) -> return */ @@ -582,6 +592,8 @@ function ltrim(string $string, string $characters = " \t\n\r\0\x0B") : string {} /** * @psalm-pure * + * @return ($string is lowercase-string ? lowercase-string : string) + * * @psalm-flow ($string) -> return */ function rtrim(string $string, string $characters = " \t\n\r\0\x0B") : string {} diff --git a/tests/FunctionCallTest.php b/tests/FunctionCallTest.php index 955cc9b13e3..be4f4c32b17 100644 --- a/tests/FunctionCallTest.php +++ b/tests/FunctionCallTest.php @@ -1785,6 +1785,33 @@ function sayHello(string $needle): void { [], '8.0', ], + 'trimSavesLowercaseAttribute' => [ + ' [ + '$b===' => 'lowercase-string', + ], + ], + 'ltrimSavesLowercaseAttribute' => [ + ' [ + '$b===' => 'lowercase-string', + ], + ], + 'rtrimSavesLowercaseAttribute' => [ + ' [ + '$b===' => 'lowercase-string', + ], + ], ]; } From 969c7a098be545cf453687ed86a68899d9a3781c Mon Sep 17 00:00:00 2001 From: Semyon <7ionmail@gmail.com> Date: Fri, 2 Sep 2022 17:37:10 +0300 Subject: [PATCH 076/178] Make ctype_digit and ctype_lower work as assertions --- .../Statements/Expression/AssertionFinder.php | 2 + tests/TypeReconciliation/ConditionalTest.php | 52 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php index 0fa53eaf5e5..1dcc97346a9 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php @@ -103,6 +103,8 @@ class AssertionFinder 'is_scalar' => ['scalar', [Type::class, 'getScalar']], 'is_iterable' => ['iterable'], 'is_countable' => ['countable'], + 'ctype_digit' => ['numeric-string', [Type::class, 'getNumericString']], + 'ctype_lower' => ['non-empty-lowercase-string', [Type::class, 'getNonEmptyLowercaseString']], ]; /** diff --git a/tests/TypeReconciliation/ConditionalTest.php b/tests/TypeReconciliation/ConditionalTest.php index ac5ee27f322..295597be4a3 100644 --- a/tests/TypeReconciliation/ConditionalTest.php +++ b/tests/TypeReconciliation/ConditionalTest.php @@ -2848,6 +2848,58 @@ function matches(string $value): bool { return true; }' ], + 'ctypeDigitMakesStringNumeric' => [ + ' [ + ' [ + '$int' => 'int<48, 57>|int<256, 1000>' + ] + ], + 'ctypeLowerMakesStringLowercase' => [ + ' [ + ' [ + '$int' => 'int<97, 122>' + ] + ], ]; } From a6710791e42aada572fb0b0f43e62eff22d68b11 Mon Sep 17 00:00:00 2001 From: Alex <93376818+sashashura@users.noreply.github.com> Date: Fri, 2 Sep 2022 19:16:42 +0100 Subject: [PATCH 077/178] Update build-phar.yml Signed-off-by: sashashura <93376818+sashashura@users.noreply.github.com> --- .github/workflows/build-phar.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build-phar.yml b/.github/workflows/build-phar.yml index 357a36011bf..ffbf8aacfe4 100644 --- a/.github/workflows/build-phar.yml +++ b/.github/workflows/build-phar.yml @@ -8,8 +8,13 @@ on: types: - published +permissions: + contents: read + jobs: pre_job: + permissions: + actions: write runs-on: ubuntu-latest outputs: should_skip: ${{ steps.skip_check.outputs.should_skip }} @@ -24,6 +29,8 @@ jobs: paths: '["bin/**", "assets/**", "build/**", "dictionaries/**", "src/**", "stubs/**", "psalm", "psalm-language-server", "psalm-plugin", "psalm-refactor", "psalter", "box.json.dist", "composer.json", "config.xsd", "keys.asc.gpg", "scoper.inc.php"]' build-phar: + permissions: + contents: write # for release needs: pre_job if: ${{ needs.pre_job.outputs.should_skip != 'true' }} runs-on: ubuntu-latest From f0a8810cf50b2afdea562282f702aa098106a062 Mon Sep 17 00:00:00 2001 From: Semyon <7ionmail@gmail.com> Date: Wed, 7 Sep 2022 15:49:35 +0300 Subject: [PATCH 078/178] Fix ctype_digit assertion bug --- .../Statements/Expression/AssertionFinder.php | 2 +- tests/TypeReconciliation/ConditionalTest.php | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php index 1dcc97346a9..1603d1655dd 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php @@ -103,7 +103,7 @@ class AssertionFinder 'is_scalar' => ['scalar', [Type::class, 'getScalar']], 'is_iterable' => ['iterable'], 'is_countable' => ['countable'], - 'ctype_digit' => ['numeric-string', [Type::class, 'getNumericString']], + 'ctype_digit' => ['=numeric-string', [Type::class, 'getNumericString']], 'ctype_lower' => ['non-empty-lowercase-string', [Type::class, 'getNonEmptyLowercaseString']], ]; diff --git a/tests/TypeReconciliation/ConditionalTest.php b/tests/TypeReconciliation/ConditionalTest.php index 295597be4a3..3b7d0148bd5 100644 --- a/tests/TypeReconciliation/ConditionalTest.php +++ b/tests/TypeReconciliation/ConditionalTest.php @@ -2862,6 +2862,20 @@ function bar(mixed $m): void } ', ], + 'ctypeDigitMakesStringNumericButDoesntProveOtherwise' => [ + ' [ ' Date: Wed, 7 Sep 2022 13:20:31 +0200 Subject: [PATCH 079/178] report invalidCasing when using a class that is not user defined too (e.g. new DateTime) --- .../Internal/Analyzer/ClassLikeAnalyzer.php | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php index 0b4934e8de0..51387fc2cdf 100644 --- a/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php @@ -361,16 +361,14 @@ public static function checkFullyQualifiedClassLikeName( || ($interface_exists && !$codebase->interfaceHasCorrectCasing($fq_class_name)) || ($enum_exists && !$codebase->classlikes->enumHasCorrectCasing($fq_class_name)) ) { - if ($codebase->classlikes->isUserDefined(strtolower($aliased_name))) { - IssueBuffer::maybeAdd( - new InvalidClass( - 'Class, interface or enum ' . $fq_class_name . ' has wrong casing', - $code_location, - $fq_class_name - ), - $suppressed_issues - ); - } + IssueBuffer::maybeAdd( + new InvalidClass( + 'Class, interface or enum ' . $fq_class_name . ' has wrong casing', + $code_location, + $fq_class_name + ), + $suppressed_issues + ); } } From 1a10654cb3fa5a3a8eac30b3d59520bf0cd00c3d Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Thu, 8 Sep 2022 12:01:14 +0200 Subject: [PATCH 080/178] fix tests --- tests/IntRangeTest.php | 2 +- tests/MethodCallTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/IntRangeTest.php b/tests/IntRangeTest.php index 5aa37992772..7d3491b73f8 100644 --- a/tests/IntRangeTest.php +++ b/tests/IntRangeTest.php @@ -687,7 +687,7 @@ function doAnalysis(): void /** @var string $secret */ $length = strlen($secret); if ($length > 16) { - throw new exception(""); + throw new Exception(""); } assert($length === 1); diff --git a/tests/MethodCallTest.php b/tests/MethodCallTest.php index 62860e8f209..f30f1115fdc 100644 --- a/tests/MethodCallTest.php +++ b/tests/MethodCallTest.php @@ -979,7 +979,7 @@ public static function new() : self { class Datetime extends \DateTime { - public static function createFromInterface(\DatetimeInterface $datetime): \DateTime + public static function createFromInterface(\DateTimeInterface $datetime): \DateTime { return parent::createFromInterface($datetime); } From 15046c932b59b8c817f16601507cc81321d5997a Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Thu, 8 Sep 2022 18:51:33 +0200 Subject: [PATCH 081/178] preg_replace with anchor will always only have 1 replacement, add limit for clarity and performance --- src/Psalm/Codebase.php | 2 +- src/Psalm/Config.php | 8 ++++---- src/Psalm/Config/Creator.php | 2 +- src/Psalm/Config/FileFilter.php | 2 +- src/Psalm/Context.php | 2 +- src/Psalm/DocComment.php | 8 ++++---- .../Internal/Analyzer/ClassLikeAnalyzer.php | 2 +- .../Internal/Analyzer/NamespaceAnalyzer.php | 2 +- .../Expression/Call/FunctionCallAnalyzer.php | 2 +- .../Statements/Expression/CallAnalyzer.php | 4 ++-- src/Psalm/Internal/Cli/LanguageServer.php | 2 +- src/Psalm/Internal/Cli/Psalm.php | 2 +- src/Psalm/Internal/Cli/Psalter.php | 2 +- src/Psalm/Internal/Cli/Refactor.php | 2 +- src/Psalm/Internal/Codebase/Analyzer.php | 2 +- src/Psalm/Internal/Codebase/ClassLikes.php | 4 ++-- src/Psalm/Internal/Codebase/Properties.php | 16 ++++++++-------- src/Psalm/Internal/MethodIdentifier.php | 4 ++-- .../Reflector/ClassLikeDocblockParser.php | 2 +- .../Reflector/ClassLikeNodeScanner.php | 4 ++-- .../Reflector/FunctionLikeDocblockParser.php | 6 +++--- src/Psalm/Internal/Scanner/DocblockParser.php | 4 ++-- 22 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/Psalm/Codebase.php b/src/Psalm/Codebase.php index cb72efa8b76..1de35369467 100644 --- a/src/Psalm/Codebase.php +++ b/src/Psalm/Codebase.php @@ -1669,7 +1669,7 @@ public function getCompletionItemsForPartialSymbol( ) { $file_contents = $this->getFileContents($file_path); - $class_name = preg_replace('/^.*\\\/', '', $fq_class_name); + $class_name = preg_replace('/^.*\\\/', '', $fq_class_name, 1); if ($aliases->uses_end) { $position = self::getPositionFromOffset($aliases->uses_end, $file_contents); diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index d9d81bfd5fb..0f6ef130ca8 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -1313,7 +1313,7 @@ public function setCustomErrorLevel(string $issue_key, string $error_level): voi private function loadFileExtensions(SimpleXMLElement $extensions): void { foreach ($extensions as $extension) { - $extension_name = preg_replace('/^\.?/', '', (string)$extension['name']); + $extension_name = preg_replace('/^\.?/', '', (string)$extension['name'], 1); $this->file_extensions[] = $extension_name; if (isset($extension['scanner'])) { @@ -1507,7 +1507,7 @@ private function getPluginClassForPath(Codebase $codebase, string $path, string public function shortenFileName(string $to): string { if (!is_file($to)) { - return preg_replace('/^' . preg_quote($this->base_dir, '/') . '/', '', $to); + return preg_replace('/^' . preg_quote($this->base_dir, '/') . '/', '', $to, 1); } $from = $this->base_dir; @@ -1679,7 +1679,7 @@ public static function getParentIssueType(string $issue_type): ?string } if (strpos($issue_type, 'Possibly') === 0) { - $stripped_issue_type = preg_replace('/^Possibly(False|Null)?/', '', $issue_type); + $stripped_issue_type = preg_replace('/^Possibly(False|Null)?/', '', $issue_type, 1); if (strpos($stripped_issue_type, 'Invalid') === false && strpos($stripped_issue_type, 'Un') !== 0) { $stripped_issue_type = 'Invalid' . $stripped_issue_type; @@ -1693,7 +1693,7 @@ public static function getParentIssueType(string $issue_type): ?string } if (preg_match('/^(False|Null)[A-Z]/', $issue_type) && !strpos($issue_type, 'Reference')) { - return preg_replace('/^(False|Null)/', 'Invalid', $issue_type); + return preg_replace('/^(False|Null)/', 'Invalid', $issue_type, 1); } if ($issue_type === 'UndefinedInterfaceMethod') { diff --git a/src/Psalm/Config/Creator.php b/src/Psalm/Config/Creator.php index f67eb5cf3cd..5a541dca888 100644 --- a/src/Psalm/Config/Creator.php +++ b/src/Psalm/Config/Creator.php @@ -242,7 +242,7 @@ private static function getPsr4Or0Paths(string $current_dir, array $composer_jso continue; } - $path = preg_replace('@[\\\\/]$@', '', $path); + $path = preg_replace('@[/\\\]$@', '', $path, 1); if ($path !== 'tests') { $nodes[] = ''; diff --git a/src/Psalm/Config/FileFilter.php b/src/Psalm/Config/FileFilter.php index 76f53d6be2e..de83e148285 100644 --- a/src/Psalm/Config/FileFilter.php +++ b/src/Psalm/Config/FileFilter.php @@ -421,7 +421,7 @@ function (): bool { */ protected static function slashify(string $str): string { - return preg_replace('/\/?$/', DIRECTORY_SEPARATOR, $str); + return rtrim( $str, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR; } public function allows(string $file_name, bool $case_sensitive = false): bool diff --git a/src/Psalm/Context.php b/src/Psalm/Context.php index 73e642af92e..1acfb9d7d37 100644 --- a/src/Psalm/Context.php +++ b/src/Psalm/Context.php @@ -752,7 +752,7 @@ public function hasVariable(string $var_name): bool return false; } - $stripped_var = preg_replace('/(->|\[).*$/', '', $var_name); + $stripped_var = preg_replace('/(->|\[).*$/', '', $var_name, 1); if ($stripped_var !== '$this' || $var_name !== $stripped_var) { $this->referenced_var_ids[$var_name] = true; diff --git a/src/Psalm/DocComment.php b/src/Psalm/DocComment.php index ae3cd6ca32a..f9cbc8287c6 100644 --- a/src/Psalm/DocComment.php +++ b/src/Psalm/DocComment.php @@ -64,9 +64,9 @@ public static function parse(string $docblock, ?int $line_number = null, bool $p { // Strip off comments. $docblock = trim($docblock); - $docblock = preg_replace('@^/\*\*@', '', $docblock); - $docblock = preg_replace('@\*/$@', '', $docblock); - $docblock = preg_replace('@^[ \t]*\*@m', '', $docblock); + $docblock = preg_replace('@^/\*\*@', '', $docblock, 1); + $docblock = preg_replace('@\*/$@', '', $docblock, 1); + $docblock = preg_replace('@^[ \t]*\*@m', '', $docblock, 1); // Normalize multi-line @specials. $lines = explode("\n", $docblock); @@ -157,7 +157,7 @@ public static function parse(string $docblock, ?int $line_number = null, bool $p // Trim any empty lines off the front, but leave the indent level if there // is one. - $docblock = preg_replace('/^\s*\n/', '', $docblock); + $docblock = preg_replace('/^\s*\n/', '', $docblock, 1); foreach ($special as $special_key => $_) { if (strpos($special_key, 'psalm-') === 0) { diff --git a/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php index 0b4934e8de0..ca4b7e9f986 100644 --- a/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php @@ -231,7 +231,7 @@ public static function checkFullyQualifiedClassLikeName( return null; } - $fq_class_name = preg_replace('/^\\\/', '', $fq_class_name); + $fq_class_name = preg_replace('/^\\\/', '', $fq_class_name, 1); if (in_array($fq_class_name, ['callable', 'iterable', 'self', 'static', 'parent'], true)) { return true; diff --git a/src/Psalm/Internal/Analyzer/NamespaceAnalyzer.php b/src/Psalm/Internal/Analyzer/NamespaceAnalyzer.php index 06413e6bbbf..a3d658b6e01 100644 --- a/src/Psalm/Internal/Analyzer/NamespaceAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/NamespaceAnalyzer.php @@ -221,7 +221,7 @@ public static function isWithinAny(string $calling_identifier, array $identifier */ public static function getNameSpaceRoot(string $fullyQualifiedClassName): string { - $root_namespace = preg_replace('/^([^\\\]+).*/', '$1', $fullyQualifiedClassName); + $root_namespace = preg_replace('/^([^\\\]+).*/', '$1', $fullyQualifiedClassName, 1); if ($root_namespace === "") { throw new InvalidArgumentException("Invalid classname \"$fullyQualifiedClassName\""); } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php index d98cf1fc825..96f26a09497 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php @@ -735,7 +735,7 @@ private static function getAnalyzeNamedExpression( if (strpos($var_type_part->value, '::')) { $parts = explode('::', strtolower($var_type_part->value)); $fq_class_name = $parts[0]; - $fq_class_name = preg_replace('/^\\\\/', '', $fq_class_name); + $fq_class_name = preg_replace('/^\\\/', '', $fq_class_name, 1); $potential_method_id = new MethodIdentifier($fq_class_name, $parts[1]); } else { $function_call_info->new_function_name = new VirtualFullyQualified( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php index 9cc9d85098e..e5c7942365a 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php @@ -522,7 +522,7 @@ public static function getFunctionIdsFromCallableArg( } if ($callable_arg instanceof PhpParser\Node\Scalar\String_) { - $potential_id = preg_replace('/^\\\/', '', $callable_arg->value); + $potential_id = preg_replace('/^\\\/', '', $callable_arg->value, 1); if (preg_match('/^[A-Za-z0-9_]+(\\\[A-Za-z0-9_]+)*(::[A-Za-z0-9_]+)?$/', $potential_id)) { return [$potential_id]; @@ -552,7 +552,7 @@ public static function getFunctionIdsFromCallableArg( } if ($class_arg instanceof PhpParser\Node\Scalar\String_) { - return [preg_replace('/^\\\/', '', $class_arg->value) . '::' . $method_name_arg->value]; + return [preg_replace('/^\\\/', '', $class_arg->value, 1) . '::' . $method_name_arg->value]; } if ($class_arg instanceof PhpParser\Node\Expr\ClassConstFetch diff --git a/src/Psalm/Internal/Cli/LanguageServer.php b/src/Psalm/Internal/Cli/LanguageServer.php index e8689ee968c..634cdee16a4 100644 --- a/src/Psalm/Internal/Cli/LanguageServer.php +++ b/src/Psalm/Internal/Cli/LanguageServer.php @@ -93,7 +93,7 @@ public static function run(array $argv): void array_map( function (string $arg) use ($valid_long_options): void { if (strpos($arg, '--') === 0 && $arg !== '--') { - $arg_name = preg_replace('/=.*$/', '', substr($arg, 2)); + $arg_name = preg_replace('/=.*$/', '', substr($arg, 2), 1); if (!in_array($arg_name, $valid_long_options, true) && !in_array($arg_name . ':', $valid_long_options, true) diff --git a/src/Psalm/Internal/Cli/Psalm.php b/src/Psalm/Internal/Cli/Psalm.php index 86031b7b614..a01a47aa05b 100644 --- a/src/Psalm/Internal/Cli/Psalm.php +++ b/src/Psalm/Internal/Cli/Psalm.php @@ -431,7 +431,7 @@ private static function validateCliArguments(array $args): void array_map( function (string $arg): void { if (strpos($arg, '--') === 0 && $arg !== '--') { - $arg_name = preg_replace('/=.*$/', '', substr($arg, 2)); + $arg_name = preg_replace('/=.*$/', '', substr($arg, 2), 1); if (!in_array($arg_name, self::LONG_OPTIONS) && !in_array($arg_name . ':', self::LONG_OPTIONS) diff --git a/src/Psalm/Internal/Cli/Psalter.php b/src/Psalm/Internal/Cli/Psalter.php index a29491475cf..7db204c5f61 100644 --- a/src/Psalm/Internal/Cli/Psalter.php +++ b/src/Psalm/Internal/Cli/Psalter.php @@ -432,7 +432,7 @@ private static function validateCliArguments(array $args): void array_map( function (string $arg): void { if (strpos($arg, '--') === 0 && $arg !== '--') { - $arg_name = preg_replace('/=.*$/', '', substr($arg, 2)); + $arg_name = preg_replace('/=.*$/', '', substr($arg, 2), 1); if ($arg_name === 'alter') { // valid option for psalm, ignored by psalter diff --git a/src/Psalm/Internal/Cli/Refactor.php b/src/Psalm/Internal/Cli/Refactor.php index 1c87f4e84a2..864e9d4aebd 100644 --- a/src/Psalm/Internal/Cli/Refactor.php +++ b/src/Psalm/Internal/Cli/Refactor.php @@ -82,7 +82,7 @@ public static function run(array $argv): void array_map( function (string $arg) use ($valid_long_options): void { if (strpos($arg, '--') === 0 && $arg !== '--') { - $arg_name = preg_replace('/=.*$/', '', substr($arg, 2)); + $arg_name = preg_replace('/=.*$/', '', substr($arg, 2), 1); if ($arg_name === 'refactor') { // valid option for psalm, ignored by psalter diff --git a/src/Psalm/Internal/Codebase/Analyzer.php b/src/Psalm/Internal/Codebase/Analyzer.php index 36c927131d3..a2d06d4eaaa 100644 --- a/src/Psalm/Internal/Codebase/Analyzer.php +++ b/src/Psalm/Internal/Codebase/Analyzer.php @@ -773,7 +773,7 @@ public function loadCachedResults(ProjectAnalyzer $project_analyzer): void $method_param_uses[$member_id] ); - $member_stub = preg_replace('/::.*$/', '::*', $member_id); + $member_stub = preg_replace('/::.*$/', '::*', $member_id, 1); if (isset($all_referencing_methods[$member_stub])) { $newly_invalidated_methods = array_merge( diff --git a/src/Psalm/Internal/Codebase/ClassLikes.php b/src/Psalm/Internal/Codebase/ClassLikes.php index 372d1da901f..659e421ecf8 100644 --- a/src/Psalm/Internal/Codebase/ClassLikes.php +++ b/src/Psalm/Internal/Codebase/ClassLikes.php @@ -185,7 +185,7 @@ private function collectPredefinedClassLikes(): void $predefined_classes = get_declared_classes(); foreach ($predefined_classes as $predefined_class) { - $predefined_class = preg_replace('/^\\\/', '', $predefined_class); + $predefined_class = preg_replace('/^\\\/', '', $predefined_class, 1); /** @psalm-suppress ArgumentTypeCoercion */ $reflection_class = new ReflectionClass($predefined_class); @@ -201,7 +201,7 @@ private function collectPredefinedClassLikes(): void $predefined_interfaces = get_declared_interfaces(); foreach ($predefined_interfaces as $predefined_interface) { - $predefined_interface = preg_replace('/^\\\/', '', $predefined_interface); + $predefined_interface = preg_replace('/^\\\/', '', $predefined_interface, 1); /** @psalm-suppress ArgumentTypeCoercion */ $reflection_class = new ReflectionClass($predefined_interface); diff --git a/src/Psalm/Internal/Codebase/Properties.php b/src/Psalm/Internal/Codebase/Properties.php index 2a0137a5db6..ba5604ea850 100644 --- a/src/Psalm/Internal/Codebase/Properties.php +++ b/src/Psalm/Internal/Codebase/Properties.php @@ -83,8 +83,8 @@ public function propertyExists( ?Context $context = null, ?CodeLocation $code_location = null ): bool { - // remove trailing backslash if it exists - $property_id = preg_replace('/^\\\\/', '', $property_id); + // remove leading backslash if it exists + $property_id = ltrim( $property_id, '\\' ); [$fq_class_name, $property_name] = explode('::$', $property_id); $fq_class_name_lc = strtolower($fq_class_name); @@ -248,8 +248,8 @@ public function getAppearingClassForProperty( public function getStorage(string $property_id): PropertyStorage { - // remove trailing backslash if it exists - $property_id = preg_replace('/^\\\\/', '', $property_id); + // remove leading backslash if it exists + $property_id = ltrim( $property_id, '\\' ); [$fq_class_name, $property_name] = explode('::$', $property_id); @@ -269,8 +269,8 @@ public function getStorage(string $property_id): PropertyStorage public function hasStorage(string $property_id): bool { - // remove trailing backslash if it exists - $property_id = preg_replace('/^\\\\/', '', $property_id); + // remove leading backslash if it exists + $property_id = ltrim( $property_id, '\\' ); [$fq_class_name, $property_name] = explode('::$', $property_id); @@ -291,8 +291,8 @@ public function getPropertyType( ?StatementsSource $source = null, ?Context $context = null ): ?Union { - // remove trailing backslash if it exists - $property_id = preg_replace('/^\\\\/', '', $property_id); + // remove leading backslash if it exists + $property_id = ltrim( $property_id, '\\' ); [$fq_class_name, $property_name] = explode('::$', $property_id); diff --git a/src/Psalm/Internal/MethodIdentifier.php b/src/Psalm/Internal/MethodIdentifier.php index cf4c81ba63d..1abb0ecded1 100644 --- a/src/Psalm/Internal/MethodIdentifier.php +++ b/src/Psalm/Internal/MethodIdentifier.php @@ -56,8 +56,8 @@ public static function fromMethodIdReference(string $method_id): self if (!static::isValidMethodIdReference($method_id)) { throw new InvalidArgumentException('Invalid method id reference provided: ' . $method_id); } - // remove trailing backslash if it exists - $method_id = preg_replace('/^\\\\/', '', $method_id); + // remove leading backslash if it exists + $method_id = ltrim($method_id, '\\'); $method_id_parts = explode('::', $method_id); return new self($method_id_parts[0], strtolower($method_id_parts[1])); } diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeDocblockParser.php b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeDocblockParser.php index 4572df37da3..aa2adb811c0 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeDocblockParser.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeDocblockParser.php @@ -506,7 +506,7 @@ protected static function addMagicPropertyToInfo( ) { $line_parts[1] = str_replace('&', '', $line_parts[1]); - $line_parts[1] = preg_replace('/,$/', '', $line_parts[1]); + $line_parts[1] = preg_replace('/,$/', '', $line_parts[1], 1); $end = $offset + strlen($line_parts[0]); diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php index 91fbfbfee14..656bab44db9 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php @@ -1838,8 +1838,8 @@ private static function getTypeAliasesFromCommentLines( $type_string = str_replace("\n", '', implode('', $var_line_parts)); - $type_string = preg_replace('/>[^>^\}]*$/', '>', $type_string); - $type_string = preg_replace('/\}[^>^\}]*$/', '}', $type_string); + $type_string = preg_replace('/>[^>^\}]*$/', '>', $type_string, 1); + $type_string = preg_replace('/\}[^>^\}]*$/', '}', $type_string, 1); try { $type_tokens = TypeTokenizer::getFullyQualifiedTokens( diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php index 341d0f63918..2eb713b9458 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php @@ -76,7 +76,7 @@ public static function parse( ) { $line_parts[1] = str_replace('&', '', $line_parts[1]); - $line_parts[1] = preg_replace('/,$/', '', $line_parts[1]); + $line_parts[1] = preg_replace('/,$/', '', $line_parts[1], 1); $end = $offset + strlen($line_parts[0]); @@ -152,7 +152,7 @@ public static function parse( throw new IncorrectDocblockException('Misplaced variable'); } - $line_parts[1] = preg_replace('/,$/', '', $line_parts[1]); + $line_parts[1] = preg_replace('/,$/', '', $line_parts[1], 1); $info->params_out[] = [ 'name' => trim($line_parts[1]), @@ -340,7 +340,7 @@ public static function parse( throw new IncorrectDocblockException('Misplaced variable'); } - $line_parts[1] = preg_replace('/,$/', '', $line_parts[1]); + $line_parts[1] = preg_replace('/,$/', '', $line_parts[1], 1); $info->globals[] = [ 'name' => $line_parts[1], diff --git a/src/Psalm/Internal/Scanner/DocblockParser.php b/src/Psalm/Internal/Scanner/DocblockParser.php index 2ddf134e62c..22ae1ed258f 100644 --- a/src/Psalm/Internal/Scanner/DocblockParser.php +++ b/src/Psalm/Internal/Scanner/DocblockParser.php @@ -111,7 +111,7 @@ public static function parse(string $docblock, int $offsetStart): ParsedDocblock // Strip the leading *, if present. $text = $lines[$k]; $text = str_replace("\t", ' ', $text); - $text = preg_replace('/^ *\*/', '', $text); + $text = preg_replace('/^ *\*/', '', $text, 1); $lines[$k] = $text; } @@ -142,7 +142,7 @@ public static function parse(string $docblock, int $offsetStart): ParsedDocblock // Trim any empty lines off the front, but leave the indent level if there // is one. - $docblock = preg_replace('/^\s*\n/', '', $docblock); + $docblock = preg_replace('/^\s*\n/', '', $docblock, 1); $parsed = new ParsedDocblock($docblock, $special, $first_line_padding ?: ''); From d0984f4e472f70da50b881f7b403cd6f1c126c42 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Thu, 8 Sep 2022 19:21:04 +0200 Subject: [PATCH 082/178] fix psalm internal errors reported by new checks --- src/Psalm/Config.php | 3 +-- src/Psalm/Internal/Codebase/ClassLikes.php | 8 -------- src/Psalm/Internal/PluginManager/ConfigFile.php | 10 +++++----- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index d9d81bfd5fb..9ac69ea7d42 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -6,7 +6,7 @@ use Composer\Semver\Constraint\Constraint; use Composer\Semver\VersionParser; use DOMDocument; -use DomElement; +use DOMElement; use InvalidArgumentException; use LogicException; use OutOfBoundsException; @@ -752,7 +752,6 @@ private static function validateXmlConfig(string $base_dir, string $file_content $psalm_nodes = $dom_document->getElementsByTagName('psalm'); - /** @var DomElement|null */ $psalm_node = $psalm_nodes->item(0); if (!$psalm_node) { diff --git a/src/Psalm/Internal/Codebase/ClassLikes.php b/src/Psalm/Internal/Codebase/ClassLikes.php index 372d1da901f..d19b202fef6 100644 --- a/src/Psalm/Internal/Codebase/ClassLikes.php +++ b/src/Psalm/Internal/Codebase/ClassLikes.php @@ -784,14 +784,6 @@ public function traitHasCorrectCase(string $fq_trait_name): bool return isset($this->existing_traits[$fq_trait_name]); } - /** - * @param lowercase-string $fq_class_name - */ - public function isUserDefined(string $fq_class_name): bool - { - return $this->classlike_storage_provider->get($fq_class_name)->user_defined; - } - public function getTraitNode(string $fq_trait_name): PhpParser\Node\Stmt\Trait_ { $fq_trait_name_lc = strtolower($fq_trait_name); diff --git a/src/Psalm/Internal/PluginManager/ConfigFile.php b/src/Psalm/Internal/PluginManager/ConfigFile.php index a945dd24931..ccabdc12b39 100644 --- a/src/Psalm/Internal/PluginManager/ConfigFile.php +++ b/src/Psalm/Internal/PluginManager/ConfigFile.php @@ -3,7 +3,7 @@ namespace Psalm\Internal\PluginManager; use DOMDocument; -use DomElement; +use DOMElement; use Psalm\Config; use RuntimeException; @@ -51,7 +51,7 @@ public function getConfig(): Config public function removePlugin(string $plugin_class): void { $config_xml = $this->readXml(); - /** @var DomElement */ + /** @var DOMElement */ $psalm_root = $config_xml->getElementsByTagName('psalm')[0]; $plugins_elements = $psalm_root->getElementsByTagName('plugins'); if (!$plugins_elements->length) { @@ -59,7 +59,7 @@ public function removePlugin(string $plugin_class): void return; } - /** @var DomElement */ + /** @var DOMElement */ $plugins_element = $plugins_elements->item(0); $plugin_elements = $plugins_element->getElementsByTagName('pluginClass'); @@ -82,7 +82,7 @@ public function removePlugin(string $plugin_class): void public function addPlugin(string $plugin_class): void { $config_xml = $this->readXml(); - /** @var DomElement */ + /** @var DOMElement */ $psalm_root = $config_xml->getElementsByTagName('psalm')->item(0); $plugins_elements = $psalm_root->getElementsByTagName('plugins'); if (!$plugins_elements->length) { @@ -91,7 +91,7 @@ public function addPlugin(string $plugin_class): void $psalm_root->appendChild($plugins_element); } } else { - /** @var DomElement */ + /** @var DOMElement */ $plugins_element = $plugins_elements->item(0); } From 249d61ec1b19550878c3598bcb6e94f79cad6754 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Thu, 8 Sep 2022 14:02:26 +0200 Subject: [PATCH 083/178] classlike_alias incorrect casing not handled correctly --- src/Psalm/Internal/Codebase/ClassLikes.php | 23 +++++++++++-------- src/Psalm/Internal/Codebase/Scanner.php | 4 ++-- .../Reflector/ExpressionScanner.php | 2 -- src/Psalm/Storage/FileStorage.php | 2 +- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/Psalm/Internal/Codebase/ClassLikes.php b/src/Psalm/Internal/Codebase/ClassLikes.php index f81e888620f..65422140d98 100644 --- a/src/Psalm/Internal/Codebase/ClassLikes.php +++ b/src/Psalm/Internal/Codebase/ClassLikes.php @@ -131,7 +131,12 @@ class ClassLikes /** * @var array */ - private $classlike_aliases = []; + private $classlike_aliases_map = []; + + /** + * @var array + */ + private $existing_classlike_aliases = []; /** * @var array @@ -750,7 +755,7 @@ public function classHasCorrectCasing(string $fq_class_name): bool return true; } - if (isset($this->classlike_aliases[strtolower($fq_class_name)])) { + if (isset($this->existing_classlike_aliases[$fq_class_name])) { return true; } @@ -759,7 +764,7 @@ public function classHasCorrectCasing(string $fq_class_name): bool public function interfaceHasCorrectCasing(string $fq_interface_name): bool { - if (isset($this->classlike_aliases[strtolower($fq_interface_name)])) { + if (isset($this->existing_classlike_aliases[$fq_interface_name])) { return true; } @@ -768,7 +773,7 @@ public function interfaceHasCorrectCasing(string $fq_interface_name): bool public function enumHasCorrectCasing(string $fq_enum_name): bool { - if (isset($this->classlike_aliases[strtolower($fq_enum_name)])) { + if (isset($this->existing_classlike_aliases[$fq_enum_name])) { return true; } @@ -777,7 +782,7 @@ public function enumHasCorrectCasing(string $fq_enum_name): bool public function traitHasCorrectCase(string $fq_trait_name): bool { - if (isset($this->classlike_aliases[strtolower($fq_trait_name)])) { + if (isset($this->existing_classlike_aliases[$fq_trait_name])) { return true; } @@ -820,12 +825,10 @@ public function getTraitNode(string $fq_trait_name): PhpParser\Node\Stmt\Trait_ throw new UnexpectedValueException('Could not locate trait statement'); } - /** - * @param lowercase-string $alias_name - */ public function addClassAlias(string $fq_class_name, string $alias_name): void { - $this->classlike_aliases[$alias_name] = $fq_class_name; + $this->classlike_aliases_map[strtolower($alias_name)] = $fq_class_name; + $this->existing_classlike_aliases[$alias_name] = true; } public function getUnAliasedName(string $alias_name): string @@ -835,7 +838,7 @@ public function getUnAliasedName(string $alias_name): string return $alias_name; } - $result = $this->classlike_aliases[$alias_name_lc] ?? $alias_name; + $result = $this->classlike_aliases_map[$alias_name_lc] ?? $alias_name; if ($result === $alias_name) { return $result; } diff --git a/src/Psalm/Internal/Codebase/Scanner.php b/src/Psalm/Internal/Codebase/Scanner.php index fb6e1bf5c63..b45044a0431 100644 --- a/src/Psalm/Internal/Codebase/Scanner.php +++ b/src/Psalm/Internal/Codebase/Scanner.php @@ -605,7 +605,7 @@ private function scanFile( } foreach ($file_storage->classlikes_in_file as $fq_classlike_name) { - $this->codebase->exhumeClassLikeStorage(strtolower($fq_classlike_name), $file_path); + $this->codebase->exhumeClassLikeStorage($fq_classlike_name, $file_path); } foreach ($file_storage->required_classes as $fq_classlike_name) { @@ -736,7 +736,7 @@ function () use ($fq_class_name): ?ReflectionClass { $new_fq_class_name_lc = strtolower($new_fq_class_name); if ($new_fq_class_name_lc !== $fq_class_name_lc) { - $classlikes->addClassAlias($new_fq_class_name, $fq_class_name_lc); + $classlikes->addClassAlias($new_fq_class_name, $fq_class_name); $fq_class_name_lc = $new_fq_class_name_lc; } diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionScanner.php index 2a6dbf71eb9..3cc4bd7350b 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionScanner.php @@ -257,8 +257,6 @@ private static function registerClassMapFunctionCall( $second_arg_value = substr($second_arg_value, 1); } - $second_arg_value = strtolower($second_arg_value); - $codebase->classlikes->addClassAlias( $first_arg_value, $second_arg_value diff --git a/src/Psalm/Storage/FileStorage.php b/src/Psalm/Storage/FileStorage.php index df09117dbea..fcde3b3a6ad 100644 --- a/src/Psalm/Storage/FileStorage.php +++ b/src/Psalm/Storage/FileStorage.php @@ -86,7 +86,7 @@ class FileStorage public $type_aliases = []; /** - * @var array + * @var array */ public $classlike_aliases = []; From c450d95727794fce33a8f372cd8777bef9b2257c Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Thu, 8 Sep 2022 14:33:20 +0200 Subject: [PATCH 084/178] fix inconsistent function naming --- src/Psalm/Codebase.php | 4 ++-- src/Psalm/Internal/Analyzer/ClassAnalyzer.php | 2 +- src/Psalm/Internal/Codebase/ClassLikes.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Psalm/Codebase.php b/src/Psalm/Codebase.php index 1de35369467..c74ba4637ae 100644 --- a/src/Psalm/Codebase.php +++ b/src/Psalm/Codebase.php @@ -793,9 +793,9 @@ public function interfaceHasCorrectCasing(string $fq_interface_name): bool return $this->classlikes->interfaceHasCorrectCasing($fq_interface_name); } - public function traitHasCorrectCase(string $fq_trait_name): bool + public function traitHasCorrectCasing(string $fq_trait_name): bool { - return $this->classlikes->traitHasCorrectCase($fq_trait_name); + return $this->classlikes->traitHasCorrectCasing($fq_trait_name); } /** diff --git a/src/Psalm/Internal/Analyzer/ClassAnalyzer.php b/src/Psalm/Internal/Analyzer/ClassAnalyzer.php index cb4e63491ba..f55fbaaa90d 100644 --- a/src/Psalm/Internal/Analyzer/ClassAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ClassAnalyzer.php @@ -1370,7 +1370,7 @@ private function analyzeTraitUse( return false; } - if (!$codebase->traitHasCorrectCase($fq_trait_name)) { + if (!$codebase->traitHasCorrectCasing($fq_trait_name)) { if (IssueBuffer::accepts( new UndefinedTrait( 'Trait ' . $fq_trait_name . ' has wrong casing', diff --git a/src/Psalm/Internal/Codebase/ClassLikes.php b/src/Psalm/Internal/Codebase/ClassLikes.php index 65422140d98..85abeeb0912 100644 --- a/src/Psalm/Internal/Codebase/ClassLikes.php +++ b/src/Psalm/Internal/Codebase/ClassLikes.php @@ -780,7 +780,7 @@ public function enumHasCorrectCasing(string $fq_enum_name): bool return isset($this->existing_enums[$fq_enum_name]); } - public function traitHasCorrectCase(string $fq_trait_name): bool + public function traitHasCorrectCasing(string $fq_trait_name): bool { if (isset($this->existing_classlike_aliases[$fq_trait_name])) { return true; From 4c6abccfb2a14c71bc9b4edea5696b6724aa43e1 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Thu, 8 Sep 2022 14:41:55 +0200 Subject: [PATCH 085/178] fix tests --- tests/Internal/Codebase/ClassLikesTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Internal/Codebase/ClassLikesTest.php b/tests/Internal/Codebase/ClassLikesTest.php index b04beb38f41..8168d17a875 100644 --- a/tests/Internal/Codebase/ClassLikesTest.php +++ b/tests/Internal/Codebase/ClassLikesTest.php @@ -30,7 +30,7 @@ public function setUp(): void public function testWillDetectClassImplementingAliasedInterface(): void { - $this->classlikes->addClassAlias('Foo', 'bar'); + $this->classlikes->addClassAlias('Foo', 'Bar'); $classStorage = new ClassLikeStorage('Baz'); $classStorage->class_implements['bar'] = 'Bar'; @@ -42,9 +42,9 @@ public function testWillDetectClassImplementingAliasedInterface(): void public function testWillResolveAliasedAliases(): void { - $this->classlikes->addClassAlias('Foo', 'bar'); - $this->classlikes->addClassAlias('Bar', 'baz'); - $this->classlikes->addClassAlias('Baz', 'qoo'); + $this->classlikes->addClassAlias('Foo', 'Bar'); + $this->classlikes->addClassAlias('Bar', 'Baz'); + $this->classlikes->addClassAlias('Baz', 'Qoo'); self::assertSame('Foo', $this->classlikes->getUnAliasedName('Qoo')); } From 6e4c1823d6da316614fa17e45264f6aa13886925 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Thu, 4 Aug 2022 00:45:33 +0200 Subject: [PATCH 086/178] partial revert nullable type for curl_multi_getcontent Fix https://github.com/vimeo/psalm/issues/8351 Partially reverts https://github.com/vimeo/psalm/commit/f28ac7377778e281c1b406251dd839f88ea4622e --- dictionaries/CallMap.php | 2 +- dictionaries/CallMap_80_delta.php | 4 ++-- dictionaries/CallMap_historical.php | 2 +- tests/Internal/Codebase/InternalCallMapHandlerTest.php | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 219e6372641..8c9eeb8a338 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1678,7 +1678,7 @@ 'curl_multi_close' => ['void', 'multi_handle'=>'CurlMultiHandle'], 'curl_multi_errno' => ['int', 'multi_handle'=>'CurlMultiHandle'], 'curl_multi_exec' => ['int', 'multi_handle'=>'CurlMultiHandle', '&w_still_running'=>'int'], -'curl_multi_getcontent' => ['?string', 'handle'=>'CurlHandle'], +'curl_multi_getcontent' => ['string', 'handle'=>'CurlHandle'], 'curl_multi_info_read' => ['array|false', 'multi_handle'=>'CurlMultiHandle', '&w_queued_messages='=>'int'], 'curl_multi_init' => ['CurlMultiHandle|false'], 'curl_multi_remove_handle' => ['int', 'multi_handle'=>'CurlMultiHandle', 'handle'=>'CurlHandle'], diff --git a/dictionaries/CallMap_80_delta.php b/dictionaries/CallMap_80_delta.php index afeb1bc4133..21f4f6665a0 100644 --- a/dictionaries/CallMap_80_delta.php +++ b/dictionaries/CallMap_80_delta.php @@ -282,8 +282,8 @@ 'new' => ['int', 'multi_handle'=>'CurlMultiHandle', '&w_still_running'=>'int'], ], 'curl_multi_getcontent' => [ - 'old' => ['?string', 'ch'=>'resource'], - 'new' => ['?string', 'handle'=>'CurlHandle'], + 'old' => ['string', 'ch'=>'resource'], + 'new' => ['string', 'handle'=>'CurlHandle'], ], 'curl_multi_info_read' => [ 'old' => ['array|false', 'mh'=>'resource', '&w_msgs_in_queue='=>'int'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index a5c4875aa71..43652148102 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -10135,7 +10135,7 @@ 'curl_multi_add_handle' => ['int', 'mh'=>'resource', 'ch'=>'resource'], 'curl_multi_close' => ['void', 'mh'=>'resource'], 'curl_multi_exec' => ['int', 'mh'=>'resource', '&w_still_running'=>'int'], - 'curl_multi_getcontent' => ['?string', 'ch'=>'resource'], + 'curl_multi_getcontent' => ['string', 'ch'=>'resource'], 'curl_multi_info_read' => ['array|false', 'mh'=>'resource', '&w_msgs_in_queue='=>'int'], 'curl_multi_init' => ['resource|false'], 'curl_multi_remove_handle' => ['int', 'mh'=>'resource', 'ch'=>'resource'], diff --git a/tests/Internal/Codebase/InternalCallMapHandlerTest.php b/tests/Internal/Codebase/InternalCallMapHandlerTest.php index 164fb4143d1..7ac15128d7b 100644 --- a/tests/Internal/Codebase/InternalCallMapHandlerTest.php +++ b/tests/Internal/Codebase/InternalCallMapHandlerTest.php @@ -343,6 +343,7 @@ class InternalCallMapHandlerTest extends TestCase 'cal_from_jd', 'collator_get_strength', 'curl_multi_init', + 'curl_multi_getcontent', // issue #8351 'date_add', 'date_date_set', 'date_diff', From 8da5f5eb1aa526aacc00982ac533f534a267508f Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Tue, 16 Aug 2022 15:48:49 +0200 Subject: [PATCH 087/178] use exceptions instead of error_log for ParserCacheProvider * use exceptions instead of error_log for ParserCacheProvider like all other cache providers do * remove duplicate code in ParserCacheProvider * use same hash as other cache providers * update Config.php cache directory creation to use same code as ParserCacheProvider --- src/Psalm/Config.php | 17 +- .../Internal/Provider/ParserCacheProvider.php | 184 ++++++++---------- .../Provider/ParserInstanceCacheProvider.php | 12 ++ 3 files changed, 109 insertions(+), 104 deletions(-) diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index 6dd9b63d125..534c8252906 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -35,6 +35,7 @@ use Psalm\Plugin\PluginEntryPointInterface; use Psalm\Progress\Progress; use Psalm\Progress\VoidProgress; +use RuntimeException; use SimpleXMLElement; use SimpleXMLIterator; use Throwable; @@ -55,7 +56,6 @@ use function clearstatcache; use function count; use function dirname; -use function error_log; use function explode; use function extension_loaded; use function file_exists; @@ -1013,8 +1013,19 @@ private static function fromXmlAndPaths( chdir($config->base_dir); } - if (is_dir($config->cache_directory) === false && @mkdir($config->cache_directory, 0777, true) === false) { - error_log('Could not create cache directory: ' . $config->cache_directory); + if (!is_dir($config->cache_directory)) { + try { + if (mkdir($config->cache_directory, 0777, true) === false) { + // any other error than directory already exists/permissions issue + throw new RuntimeException('Failed to create Psalm cache directory for unknown reasons'); + } + } catch (RuntimeException $e) { + if (!is_dir($config->cache_directory)) { + // rethrow the error with default message + // it contains the reason why creation failed + throw $e; + } + } } if ($cwd) { diff --git a/src/Psalm/Internal/Provider/ParserCacheProvider.php b/src/Psalm/Internal/Provider/ParserCacheProvider.php index c1c30c27b76..dc36c13f54c 100644 --- a/src/Psalm/Internal/Provider/ParserCacheProvider.php +++ b/src/Psalm/Internal/Provider/ParserCacheProvider.php @@ -7,12 +7,13 @@ use Psalm\Config; use Psalm\Internal\Provider\Providers; use RuntimeException; +use UnexpectedValueException; use function clearstatcache; -use function error_log; use function file_put_contents; use function filemtime; use function gettype; +use function hash; use function igbinary_serialize; use function igbinary_unserialize; use function is_array; @@ -21,7 +22,6 @@ use function is_writable; use function json_decode; use function json_encode; -use function md5; use function mkdir; use function scandir; use function serialize; @@ -42,31 +42,33 @@ class ParserCacheProvider private const PARSER_CACHE_DIRECTORY = 'php-parser'; private const FILE_CONTENTS_CACHE_DIRECTORY = 'file-caches'; + /** + * @var Config + */ + private $config; + /** * A map of filename hashes to contents hashes * * @var array|null */ - private $existing_file_content_hashes; + protected $existing_file_content_hashes; /** * A map of recently-added filename hashes to contents hashes * * @var array */ - private $new_file_content_hashes = []; + protected $new_file_content_hashes = []; /** * @var bool */ private $use_file_cache; - /** @var bool */ - private $use_igbinary; - public function __construct(Config $config, bool $use_file_cache = true) { - $this->use_igbinary = $config->use_igbinary; + $this->config = $config; $this->use_file_cache = $use_file_cache; } @@ -78,28 +80,22 @@ public function loadStatementsFromCache( int $file_modified_time, string $file_content_hash ): ?array { - $root_cache_directory = Config::getInstance()->getCacheDirectory(); - - if (!$root_cache_directory) { + if (!$this->use_file_cache) { return null; } - $file_cache_key = $this->getParserCacheKey( - $file_path - ); + $cache_location = $this->getCacheLocationForPath($file_path, self::PARSER_CACHE_DIRECTORY); - $parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::PARSER_CACHE_DIRECTORY; + $file_cache_key = $this->getParserCacheKey($file_path); $file_content_hashes = $this->new_file_content_hashes + $this->getExistingFileContentHashes(); - $cache_location = $parser_cache_directory . DIRECTORY_SEPARATOR . $file_cache_key; - if (isset($file_content_hashes[$file_cache_key]) && $file_content_hash === $file_content_hashes[$file_cache_key] && is_readable($cache_location) && filemtime($cache_location) > $file_modified_time ) { - if ($this->use_igbinary) { + if ($this->config->use_igbinary) { /** @var list */ $stmts = igbinary_unserialize(Providers::safeFileGetContents($cache_location)); } else { @@ -118,22 +114,14 @@ public function loadStatementsFromCache( */ public function loadExistingStatementsFromCache(string $file_path): ?array { - $root_cache_directory = Config::getInstance()->getCacheDirectory(); - - if (!$root_cache_directory) { + if (!$this->use_file_cache) { return null; } - $file_cache_key = $this->getParserCacheKey( - $file_path - ); - - $parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::PARSER_CACHE_DIRECTORY; - - $cache_location = $parser_cache_directory . DIRECTORY_SEPARATOR . $file_cache_key; + $cache_location = $this->getCacheLocationForPath($file_path, self::PARSER_CACHE_DIRECTORY); if (is_readable($cache_location)) { - if ($this->use_igbinary) { + if ($this->config->use_igbinary) { /** @var list */ return igbinary_unserialize(Providers::safeFileGetContents($cache_location)) ?: null; } @@ -151,19 +139,7 @@ public function loadExistingFileContentsFromCache(string $file_path): ?string return null; } - $root_cache_directory = Config::getInstance()->getCacheDirectory(); - - if (!$root_cache_directory) { - return null; - } - - $file_cache_key = $this->getParserCacheKey( - $file_path - ); - - $parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::FILE_CONTENTS_CACHE_DIRECTORY; - - $cache_location = $parser_cache_directory . DIRECTORY_SEPARATOR . $file_cache_key; + $cache_location = $this->getCacheLocationForPath($file_path, self::FILE_CONTENTS_CACHE_DIRECTORY); if (is_readable($cache_location)) { return Providers::safeFileGetContents($cache_location); @@ -177,35 +153,37 @@ public function loadExistingFileContentsFromCache(string $file_path): ?string */ private function getExistingFileContentHashes(): array { - $config = Config::getInstance(); - $root_cache_directory = $config->getCacheDirectory(); + if (!$this->use_file_cache) { + return []; + } if ($this->existing_file_content_hashes === null) { + $root_cache_directory = $this->config->getCacheDirectory(); $file_hashes_path = $root_cache_directory . DIRECTORY_SEPARATOR . self::FILE_HASHES; - if ($root_cache_directory && is_readable($file_hashes_path)) { - $hashes_encoded = Providers::safeFileGetContents($file_hashes_path); - if (!$hashes_encoded) { - error_log('Unexpected value when loading from file content hashes'); - $this->existing_file_content_hashes = []; - - return []; - } + if (!$root_cache_directory) { + throw new UnexpectedValueException('No cache directory defined'); + } - $hashes_decoded = json_decode($hashes_encoded, true); + if (!is_readable($file_hashes_path)) { + // might not exist yet + $this->existing_file_content_hashes = []; + return $this->existing_file_content_hashes; + } - if (!is_array($hashes_decoded)) { - error_log('Unexpected value ' . gettype($hashes_decoded)); - $this->existing_file_content_hashes = []; + $hashes_encoded = Providers::safeFileGetContents($file_hashes_path); + if (!$hashes_encoded) { + throw new UnexpectedValueException('File content hashes should be in cache'); + } - return []; - } + $hashes_decoded = json_decode($hashes_encoded, true); - /** @var array $hashes_decoded */ - $this->existing_file_content_hashes = $hashes_decoded; - } else { - $this->existing_file_content_hashes = []; + if (!is_array($hashes_decoded)) { + throw new UnexpectedValueException('File content hashes are of invalid type ' . gettype($hashes_decoded)); } + + /** @var array $hashes_decoded */ + $this->existing_file_content_hashes = $hashes_decoded; } return $this->existing_file_content_hashes; @@ -220,31 +198,18 @@ public function saveStatementsToCache( array $stmts, bool $touch_only ): void { - $root_cache_directory = Config::getInstance()->getCacheDirectory(); - - if (!$root_cache_directory) { - return; - } - - $file_cache_key = $this->getParserCacheKey( - $file_path - ); - - $parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::PARSER_CACHE_DIRECTORY; - - $cache_location = $parser_cache_directory . DIRECTORY_SEPARATOR . $file_cache_key; + $cache_location = $this->getCacheLocationForPath($file_path, self::PARSER_CACHE_DIRECTORY, !$touch_only); if ($touch_only) { touch($cache_location); } else { - $this->createCacheDirectory($parser_cache_directory); - - if ($this->use_igbinary) { + if ($this->config->use_igbinary) { file_put_contents($cache_location, igbinary_serialize($stmts), LOCK_EX); } else { file_put_contents($cache_location, serialize($stmts), LOCK_EX); } + $file_cache_key = $this->getParserCacheKey($file_path); $this->new_file_content_hashes[$file_cache_key] = $file_content_hash; } } @@ -268,7 +233,11 @@ public function addNewFileContentHashes(array $file_content_hashes): void public function saveFileContentHashes(): void { - $root_cache_directory = Config::getInstance()->getCacheDirectory(); + if (!$this->use_file_cache) { + return; + } + + $root_cache_directory = $this->config->getCacheDirectory(); if (!$root_cache_directory) { return; @@ -298,28 +267,17 @@ public function cacheFileContents(string $file_path, string $file_contents): voi return; } - $root_cache_directory = Config::getInstance()->getCacheDirectory(); - - if (!$root_cache_directory) { - return; - } - - $file_cache_key = $this->getParserCacheKey( - $file_path - ); - - $parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::FILE_CONTENTS_CACHE_DIRECTORY; - - $cache_location = $parser_cache_directory . DIRECTORY_SEPARATOR . $file_cache_key; - - $this->createCacheDirectory($parser_cache_directory); + $cache_location = $this->getCacheLocationForPath($file_path, self::FILE_CONTENTS_CACHE_DIRECTORY, true); file_put_contents($cache_location, $file_contents, LOCK_EX); } public function deleteOldParserCaches(float $time_before): int { - $cache_directory = Config::getInstance()->getCacheDirectory(); + $cache_directory = $this->config->getCacheDirectory(); + + $this->existing_file_content_hashes = null; + $this->new_file_content_hashes = []; if (!$cache_directory) { return 0; @@ -349,22 +307,46 @@ public function deleteOldParserCaches(float $time_before): int return $removed_count; } - private function getParserCacheKey(string $file_name): string + private function getParserCacheKey(string $file_path): string { - return md5($file_name) . ($this->use_igbinary ? '-igbinary' : '') . '-r'; + if (PHP_VERSION_ID >= 80100) { + $hash = hash('xxh128', $file_path); + } else { + $hash = hash('md4', $file_path); + } + + return $hash . ($this->config->use_igbinary ? '-igbinary' : '') . '-r'; } - private function createCacheDirectory(string $parser_cache_directory): void + + private function getCacheLocationForPath(string $file_path, string $subdirectory, bool $create_directory = false): string { - if (!is_dir($parser_cache_directory)) { + $root_cache_directory = $this->config->getCacheDirectory(); + + if (!$root_cache_directory) { + throw new UnexpectedValueException('No cache directory defined'); + } + + $parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . $subdirectory; + + if ($create_directory && !is_dir($parser_cache_directory)) { try { - mkdir($parser_cache_directory, 0777, true); + if (mkdir($parser_cache_directory, 0777, true) === false) { + // any other error than directory already exists/permissions issue + throw new RuntimeException('Failed to create ' . $parser_cache_directory . ' cache directory for unknown reasons'); + } } catch (RuntimeException $e) { // Race condition (#4483) if (!is_dir($parser_cache_directory)) { - error_log('Could not create parser cache directory: ' . $parser_cache_directory); + // rethrow the error with default message + // it contains the reason why creation failed + throw $e; } } } + + return $parser_cache_directory + . DIRECTORY_SEPARATOR + . $this->getParserCacheKey($file_path); } } diff --git a/tests/Internal/Provider/ParserInstanceCacheProvider.php b/tests/Internal/Provider/ParserInstanceCacheProvider.php index 9b81bfcef87..766772cd600 100644 --- a/tests/Internal/Provider/ParserInstanceCacheProvider.php +++ b/tests/Internal/Provider/ParserInstanceCacheProvider.php @@ -82,6 +82,18 @@ public function cacheFileContents(string $file_path, string $file_contents): voi $this->file_contents_cache[$file_path] = $file_contents; } + public function deleteOldParserCaches(float $time_before): int + { + $this->existing_file_content_hashes = null; + $this->new_file_content_hashes = []; + + $this->file_contents_cache = []; + $this->file_content_hash = []; + $this->statements_cache = []; + $this->statements_cache_time = []; + return 0; + } + public function saveFileContentHashes(): void { } From 4726454f49095bdfca0e024f850af079362927a8 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Tue, 16 Aug 2022 15:53:28 +0200 Subject: [PATCH 088/178] update leftover md5 in provider to commonly used hash Revert "update leftover md5 in provider to commonly used hash" This reverts commit 66337ecf50446dca8650a0812ebfe516d1993e06. partially put back Update StatementsProvider.php --- src/Psalm/Internal/Provider/StatementsProvider.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Provider/StatementsProvider.php b/src/Psalm/Internal/Provider/StatementsProvider.php index 431cb1585d4..75e47809b6e 100644 --- a/src/Psalm/Internal/Provider/StatementsProvider.php +++ b/src/Psalm/Internal/Provider/StatementsProvider.php @@ -26,6 +26,7 @@ use function array_merge; use function count; use function filemtime; +use function hash; use function md5; use function strlen; use function strpos; @@ -132,7 +133,11 @@ public function getStatementsForFile(string $file_path, string $php_version, ?Pr $config = Config::getInstance(); - $file_content_hash = md5($version . $file_contents); + if (PHP_VERSION_ID >= 80100) { + $file_content_hash = hash('xxh128', $version . $file_contents); + } else { + $file_content_hash = hash('md4', $version . $file_contents); + } if (!$this->parser_cache_provider || (!$config->isInProjectDirs($file_path) && strpos($file_path, 'vendor')) @@ -239,6 +244,7 @@ function (int $_): bool { array_flip($unchanged_signature_members) ); + // do NOT change this to hash, it will fail on Windows for whatever reason $file_path_hash = md5($file_path); $changed_members = array_map( From 8ac86f0a4d99d65ee8932c0a74e79d2b6a5c2f9a Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Thu, 18 Aug 2022 13:39:46 +0200 Subject: [PATCH 089/178] use consistent race condition dir creation code in all places in cache --- .../ClassLikeStorageCacheProvider.php | 15 +++++++- .../Provider/FileReferenceCacheProvider.php | 36 +++++++++++++------ .../Provider/FileStorageCacheProvider.php | 15 +++++++- 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php b/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php index c7fa19acd0c..a6aa8e7b129 100644 --- a/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php +++ b/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php @@ -5,6 +5,7 @@ use Psalm\Config; use Psalm\Internal\Provider\Providers; use Psalm\Storage\ClassLikeStorage; +use RuntimeException; use UnexpectedValueException; use function array_merge; @@ -168,7 +169,19 @@ private function getCacheLocationForClass( $parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::CLASS_CACHE_DIRECTORY; if ($create_directory && !is_dir($parser_cache_directory)) { - mkdir($parser_cache_directory, 0777, true); + try { + if (mkdir($parser_cache_directory, 0777, true) === false) { + // any other error than directory already exists/permissions issue + throw new RuntimeException('Failed to create ' . $parser_cache_directory . ' cache directory for unknown reasons'); + } + } catch (RuntimeException $e) { + // Race condition (#4483) + if (!is_dir($parser_cache_directory)) { + // rethrow the error with default message + // it contains the reason why creation failed + throw $e; + } + } } $data = $file_path ? strtolower($file_path) . ' ' : ''; diff --git a/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php b/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php index e512aacf29b..76f12afb09f 100644 --- a/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php +++ b/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php @@ -5,6 +5,7 @@ use Psalm\Config; use Psalm\Internal\Codebase\Analyzer; use Psalm\Internal\Provider\Providers; +use RuntimeException; use UnexpectedValueException; use function file_exists; @@ -12,6 +13,7 @@ use function igbinary_serialize; use function igbinary_unserialize; use function is_array; +use function is_dir; use function is_readable; use function mkdir; use function serialize; @@ -992,18 +994,32 @@ public function setConfigHashCache(string $hash): void { $cache_directory = Config::getInstance()->getCacheDirectory(); - if ($cache_directory) { - if (!file_exists($cache_directory)) { - mkdir($cache_directory, 0777, true); + if (!$cache_directory) { + return; + } + + if (!is_dir($cache_directory)) { + try { + if (mkdir($cache_directory, 0777, true) === false) { + // any other error than directory already exists/permissions issue + throw new RuntimeException('Failed to create ' . $cache_directory . ' cache directory for unknown reasons'); + } + } catch (RuntimeException $e) { + // Race condition (#4483) + if (!is_dir($cache_directory)) { + // rethrow the error with default message + // it contains the reason why creation failed + throw $e; + } } + } - $config_hash_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::CONFIG_HASH_CACHE_NAME; + $config_hash_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::CONFIG_HASH_CACHE_NAME; - file_put_contents( - $config_hash_cache_location, - $hash, - LOCK_EX - ); - } + file_put_contents( + $config_hash_cache_location, + $hash, + LOCK_EX + ); } } diff --git a/src/Psalm/Internal/Provider/FileStorageCacheProvider.php b/src/Psalm/Internal/Provider/FileStorageCacheProvider.php index 2f9279f12ae..ddcbf22a633 100644 --- a/src/Psalm/Internal/Provider/FileStorageCacheProvider.php +++ b/src/Psalm/Internal/Provider/FileStorageCacheProvider.php @@ -5,6 +5,7 @@ use Psalm\Config; use Psalm\Internal\Provider\Providers; use Psalm\Storage\FileStorage; +use RuntimeException; use UnexpectedValueException; use function array_merge; @@ -168,7 +169,19 @@ private function getCacheLocationForPath(string $file_path, bool $create_directo $parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::FILE_STORAGE_CACHE_DIRECTORY; if ($create_directory && !is_dir($parser_cache_directory)) { - mkdir($parser_cache_directory, 0777, true); + try { + if (mkdir($parser_cache_directory, 0777, true) === false) { + // any other error than directory already exists/permissions issue + throw new RuntimeException('Failed to create ' . $parser_cache_directory . ' cache directory for unknown reasons'); + } + } catch (RuntimeException $e) { + // Race condition (#4483) + if (!is_dir($parser_cache_directory)) { + // rethrow the error with default message + // it contains the reason why creation failed + throw $e; + } + } } if (PHP_VERSION_ID >= 80100) { From 978f37e421a1c4a96cc0b9b0445deca97ea443aa Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Wed, 24 Aug 2022 12:18:46 +0200 Subject: [PATCH 090/178] improve unlinking potential race condition * fix rare race condition on file cache unlink * remove unnecessary reset() * improve code readability using variable --- src/Psalm/Config.php | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index 534c8252906..380e8399751 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -2298,20 +2298,29 @@ public static function removeCacheDirectory(string $dir): void continue; } + $full_path = $dir . '/' . $object; + // if it was deleted in the meantime/race condition with other psalm process - if (!file_exists($dir . '/' . $object)) { + if (!file_exists($full_path)) { continue; } - if (filetype($dir . '/' . $object) === 'dir') { - self::removeCacheDirectory($dir . '/' . $object); + if (filetype($full_path) === 'dir') { + self::removeCacheDirectory($full_path); } else { - unlink($dir . '/' . $object); + try { + unlink($full_path); + } catch (RuntimeException $e) { + clearstatcache(true, $full_path); + if (file_exists($full_path)) { + // rethrow the error with default message + // it contains the reason why deletion failed + throw $e; + } + } } } - reset($objects); - // may have been removed in the meantime clearstatcache(true, $dir); if (is_dir($dir)) { From 62df25a741a3a5f24c373e7ee8f7b66bc7e954b7 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Thu, 8 Sep 2022 23:57:12 +0200 Subject: [PATCH 091/178] fix test cache inconsistency --- tests/Internal/Provider/FakeParserCacheProvider.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/Internal/Provider/FakeParserCacheProvider.php b/tests/Internal/Provider/FakeParserCacheProvider.php index 06b31afd84b..36b1dbb3d50 100644 --- a/tests/Internal/Provider/FakeParserCacheProvider.php +++ b/tests/Internal/Provider/FakeParserCacheProvider.php @@ -33,6 +33,14 @@ public function cacheFileContents(string $file_path, string $file_contents): voi { } + public function deleteOldParserCaches(float $time_before): int + { + $this->existing_file_content_hashes = null; + $this->new_file_content_hashes = []; + + return 0; + } + public function saveFileContentHashes(): void { } From 4b2841580e45015136fbbf10f634bdfffe61f341 Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Tue, 13 Sep 2022 09:50:21 -0400 Subject: [PATCH 092/178] Pin version of PHPStan phpdoc parser for slevomat rules --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index ddbe93f4c60..b710a9bcb83 100644 --- a/composer.json +++ b/composer.json @@ -52,6 +52,7 @@ "phpunit/phpunit": "^9.0", "psalm/plugin-phpunit": "^0.16", "slevomat/coding-standard": "^7.0", + "phpstan/phpdoc-parser": "1.6.4", "squizlabs/php_codesniffer": "^3.5", "symfony/process": "^4.3 || ^5.0 || ^6.0", "weirdan/prophecy-shim": "^1.0 || ^2.0" From 6f298d2af2c7f11e1176b19c73a8d9363c88fc29 Mon Sep 17 00:00:00 2001 From: Matt Brown Date: Tue, 13 Sep 2022 12:33:47 -0400 Subject: [PATCH 093/178] Fix phpcs violations --- src/Psalm/Config.php | 1 - src/Psalm/Config/FileFilter.php | 4 ++-- src/Psalm/Internal/Codebase/Properties.php | 10 ++++----- src/Psalm/Internal/MethodIdentifier.php | 2 +- .../ClassLikeStorageCacheProvider.php | 4 +++- .../Provider/FileReferenceCacheProvider.php | 12 ++++++++--- .../Provider/FileStorageCacheProvider.php | 4 +++- .../Internal/Provider/ParserCacheProvider.php | 21 +++++++++++++------ src/Psalm/Internal/Provider/Providers.php | 2 +- .../ArrayReduceReturnTypeProvider.php | 4 +++- .../Internal/Provider/StatementsProvider.php | 2 ++ 11 files changed, 44 insertions(+), 22 deletions(-) diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index 380e8399751..c97021c5ed3 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -6,7 +6,6 @@ use Composer\Semver\Constraint\Constraint; use Composer\Semver\VersionParser; use DOMDocument; -use DOMElement; use InvalidArgumentException; use LogicException; use OutOfBoundsException; diff --git a/src/Psalm/Config/FileFilter.php b/src/Psalm/Config/FileFilter.php index de83e148285..37e370ff5db 100644 --- a/src/Psalm/Config/FileFilter.php +++ b/src/Psalm/Config/FileFilter.php @@ -15,10 +15,10 @@ use function is_dir; use function is_iterable; use function preg_match; -use function preg_replace; use function readlink; use function realpath; use function restore_error_handler; +use function rtrim; use function set_error_handler; use function str_replace; use function stripos; @@ -421,7 +421,7 @@ function (): bool { */ protected static function slashify(string $str): string { - return rtrim( $str, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR; + return rtrim($str, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; } public function allows(string $file_name, bool $case_sensitive = false): bool diff --git a/src/Psalm/Internal/Codebase/Properties.php b/src/Psalm/Internal/Codebase/Properties.php index ba5604ea850..94ff1ff08a1 100644 --- a/src/Psalm/Internal/Codebase/Properties.php +++ b/src/Psalm/Internal/Codebase/Properties.php @@ -15,7 +15,7 @@ use UnexpectedValueException; use function explode; -use function preg_replace; +use function ltrim; use function strtolower; /** @@ -84,7 +84,7 @@ public function propertyExists( ?CodeLocation $code_location = null ): bool { // remove leading backslash if it exists - $property_id = ltrim( $property_id, '\\' ); + $property_id = ltrim($property_id, '\\'); [$fq_class_name, $property_name] = explode('::$', $property_id); $fq_class_name_lc = strtolower($fq_class_name); @@ -249,7 +249,7 @@ public function getAppearingClassForProperty( public function getStorage(string $property_id): PropertyStorage { // remove leading backslash if it exists - $property_id = ltrim( $property_id, '\\' ); + $property_id = ltrim($property_id, '\\'); [$fq_class_name, $property_name] = explode('::$', $property_id); @@ -270,7 +270,7 @@ public function getStorage(string $property_id): PropertyStorage public function hasStorage(string $property_id): bool { // remove leading backslash if it exists - $property_id = ltrim( $property_id, '\\' ); + $property_id = ltrim($property_id, '\\'); [$fq_class_name, $property_name] = explode('::$', $property_id); @@ -292,7 +292,7 @@ public function getPropertyType( ?Context $context = null ): ?Union { // remove leading backslash if it exists - $property_id = ltrim( $property_id, '\\' ); + $property_id = ltrim($property_id, '\\'); [$fq_class_name, $property_name] = explode('::$', $property_id); diff --git a/src/Psalm/Internal/MethodIdentifier.php b/src/Psalm/Internal/MethodIdentifier.php index 1abb0ecded1..309af8e5259 100644 --- a/src/Psalm/Internal/MethodIdentifier.php +++ b/src/Psalm/Internal/MethodIdentifier.php @@ -6,7 +6,7 @@ use function explode; use function is_string; -use function preg_replace; +use function ltrim; use function strpos; use function strtolower; diff --git a/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php b/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php index a6aa8e7b129..abd34da2d9c 100644 --- a/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php +++ b/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php @@ -172,7 +172,9 @@ private function getCacheLocationForClass( try { if (mkdir($parser_cache_directory, 0777, true) === false) { // any other error than directory already exists/permissions issue - throw new RuntimeException('Failed to create ' . $parser_cache_directory . ' cache directory for unknown reasons'); + throw new RuntimeException( + 'Failed to create ' . $parser_cache_directory . ' cache directory for unknown reasons' + ); } } catch (RuntimeException $e) { // Race condition (#4483) diff --git a/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php b/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php index 76f12afb09f..a1b3eb1a10c 100644 --- a/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php +++ b/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php @@ -942,10 +942,14 @@ public function getTypeCoverage() ) { if ($this->config->use_igbinary) { /** @var array */ - $type_coverage_cache = igbinary_unserialize(Providers::safeFileGetContents($type_coverage_cache_location)); + $type_coverage_cache = igbinary_unserialize( + Providers::safeFileGetContents($type_coverage_cache_location) + ); } else { /** @var array */ - $type_coverage_cache = unserialize(Providers::safeFileGetContents($type_coverage_cache_location)); + $type_coverage_cache = unserialize( + Providers::safeFileGetContents($type_coverage_cache_location) + ); } return $type_coverage_cache; @@ -1002,7 +1006,9 @@ public function setConfigHashCache(string $hash): void try { if (mkdir($cache_directory, 0777, true) === false) { // any other error than directory already exists/permissions issue - throw new RuntimeException('Failed to create ' . $cache_directory . ' cache directory for unknown reasons'); + throw new RuntimeException( + 'Failed to create ' . $cache_directory . ' cache directory for unknown reasons' + ); } } catch (RuntimeException $e) { // Race condition (#4483) diff --git a/src/Psalm/Internal/Provider/FileStorageCacheProvider.php b/src/Psalm/Internal/Provider/FileStorageCacheProvider.php index ddcbf22a633..ede2789a817 100644 --- a/src/Psalm/Internal/Provider/FileStorageCacheProvider.php +++ b/src/Psalm/Internal/Provider/FileStorageCacheProvider.php @@ -172,7 +172,9 @@ private function getCacheLocationForPath(string $file_path, bool $create_directo try { if (mkdir($parser_cache_directory, 0777, true) === false) { // any other error than directory already exists/permissions issue - throw new RuntimeException('Failed to create ' . $parser_cache_directory . ' cache directory for unknown reasons'); + throw new RuntimeException( + 'Failed to create ' . $parser_cache_directory . ' cache directory for unknown reasons' + ); } } catch (RuntimeException $e) { // Race condition (#4483) diff --git a/src/Psalm/Internal/Provider/ParserCacheProvider.php b/src/Psalm/Internal/Provider/ParserCacheProvider.php index dc36c13f54c..8c7dac6e0a4 100644 --- a/src/Psalm/Internal/Provider/ParserCacheProvider.php +++ b/src/Psalm/Internal/Provider/ParserCacheProvider.php @@ -31,6 +31,7 @@ use const DIRECTORY_SEPARATOR; use const LOCK_EX; +use const PHP_VERSION_ID; use const SCANDIR_SORT_NONE; /** @@ -179,7 +180,9 @@ private function getExistingFileContentHashes(): array $hashes_decoded = json_decode($hashes_encoded, true); if (!is_array($hashes_decoded)) { - throw new UnexpectedValueException('File content hashes are of invalid type ' . gettype($hashes_decoded)); + throw new UnexpectedValueException( + 'File content hashes are of invalid type ' . gettype($hashes_decoded) + ); } /** @var array $hashes_decoded */ @@ -243,8 +246,9 @@ public function saveFileContentHashes(): void return; } - // directory was removed - // most likely due to a race condition with other psalm instances that were manually started at the same time + // directory was removed most likely due to a race condition + // with other psalm instances that were manually started at + // the same time clearstatcache(true, $root_cache_directory); if (!is_dir($root_cache_directory)) { return; @@ -319,8 +323,11 @@ private function getParserCacheKey(string $file_path): string } - private function getCacheLocationForPath(string $file_path, string $subdirectory, bool $create_directory = false): string - { + private function getCacheLocationForPath( + string $file_path, + string $subdirectory, + bool $create_directory = false + ): string { $root_cache_directory = $this->config->getCacheDirectory(); if (!$root_cache_directory) { @@ -333,7 +340,9 @@ private function getCacheLocationForPath(string $file_path, string $subdirectory try { if (mkdir($parser_cache_directory, 0777, true) === false) { // any other error than directory already exists/permissions issue - throw new RuntimeException('Failed to create ' . $parser_cache_directory . ' cache directory for unknown reasons'); + throw new RuntimeException( + 'Failed to create ' . $parser_cache_directory . ' cache directory for unknown reasons' + ); } } catch (RuntimeException $e) { // Race condition (#4483) diff --git a/src/Psalm/Internal/Provider/Providers.php b/src/Psalm/Internal/Provider/Providers.php index 8d246b2d1f9..f8392f88242 100644 --- a/src/Psalm/Internal/Provider/Providers.php +++ b/src/Psalm/Internal/Provider/Providers.php @@ -100,7 +100,7 @@ public static function safeFileGetContents(string $path): string $file_size = filesize($path); $content = ''; - if ( $file_size > 0 ) { + if ($file_size > 0) { $content = (string) fread($fp, $file_size); } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReduceReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReduceReturnTypeProvider.php index 4632eaecefa..d67a4634493 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReduceReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReduceReturnTypeProvider.php @@ -100,7 +100,9 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $initial_type = $reduce_return_type; - if ($closure_types = $function_call_arg_type->getClosureTypes() ?: $function_call_arg_type->getCallableTypes()) { + $closure_types = $function_call_arg_type->getClosureTypes() ?: $function_call_arg_type->getCallableTypes(); + + if ($closure_types) { $closure_atomic_type = reset($closure_types); $closure_return_type = $closure_atomic_type->return_type ?: Type::getMixed(); diff --git a/src/Psalm/Internal/Provider/StatementsProvider.php b/src/Psalm/Internal/Provider/StatementsProvider.php index 75e47809b6e..7e1ad9f77be 100644 --- a/src/Psalm/Internal/Provider/StatementsProvider.php +++ b/src/Psalm/Internal/Provider/StatementsProvider.php @@ -31,6 +31,8 @@ use function strlen; use function strpos; +use const PHP_VERSION_ID; + /** * @internal */ From 7429bc203e118ba9ccbfba58de1a43dee25fc5f3 Mon Sep 17 00:00:00 2001 From: Matt Brown Date: Tue, 13 Sep 2022 12:35:16 -0400 Subject: [PATCH 094/178] Ignore php-parser issue --- psalm.xml.dist | 1 + 1 file changed, 1 insertion(+) diff --git a/psalm.xml.dist b/psalm.xml.dist index 135cadc6691..e1ba4dab41d 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -34,6 +34,7 @@ + From 95bb71f8a2d7f3eadc3e42fcfa27ab72641d32f4 Mon Sep 17 00:00:00 2001 From: Matt Brown Date: Tue, 13 Sep 2022 12:39:15 -0400 Subject: [PATCH 095/178] Support PHP 7.1 in require-dev restriction --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b710a9bcb83..72d44663cd6 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "phpunit/phpunit": "^9.0", "psalm/plugin-phpunit": "^0.16", "slevomat/coding-standard": "^7.0", - "phpstan/phpdoc-parser": "1.6.4", + "phpstan/phpdoc-parser": "1.2.* || 1.6.4", "squizlabs/php_codesniffer": "^3.5", "symfony/process": "^4.3 || ^5.0 || ^6.0", "weirdan/prophecy-shim": "^1.0 || ^2.0" From 17ca8ef014442f5793c08faddc36ceca0a0d15d2 Mon Sep 17 00:00:00 2001 From: George Steel Date: Mon, 12 Sep 2022 13:13:44 +0100 Subject: [PATCH 096/178] `date_get_last_errors()`, `DateTime::getLastErrors()` may return false Up to PHP 8.2, these functions return false if no previous date operations have been performed. In PHP 8.2, false is returned after a date operation that yields neither warnings nor errors: https://3v4l.org/HBq0q https://3v4l.org/3QsKY Signed-off-by: George Steel --- dictionaries/CallMap.php | 6 +++--- dictionaries/CallMap_historical.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 1d89ed805ae..60003565b66 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1723,7 +1723,7 @@ 'date_default_timezone_set' => ['bool', 'timezoneId'=>'string'], 'date_diff' => ['DateInterval|false', 'baseObject'=>'DateTimeInterface', 'targetObject'=>'DateTimeInterface', 'absolute='=>'bool'], 'date_format' => ['string', 'object'=>'DateTimeInterface', 'format'=>'string'], -'date_get_last_errors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], +'date_get_last_errors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}|false'], 'date_interval_create_from_date_string' => ['DateInterval', 'datetime'=>'string'], 'date_interval_format' => ['string', 'object'=>'DateInterval', 'format'=>'string'], 'date_isodate_set' => ['DateTime|false', 'object'=>'DateTime', 'year'=>'int', 'week'=>'int', 'dayOfWeek='=>'int|mixed'], @@ -1782,7 +1782,7 @@ 'DateTime::createFromInterface' => ['self', 'object' => 'DateTimeInterface'], 'DateTime::diff' => ['DateInterval|false', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTime::format' => ['string', 'format'=>'string'], -'DateTime::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], +'DateTime::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}|false'], 'DateTime::getOffset' => ['int'], 'DateTime::getTimestamp' => ['int|false'], 'DateTime::getTimezone' => ['DateTimeZone|false'], @@ -1796,7 +1796,7 @@ 'DateTimeImmutable::__set_state' => ['static', 'array'=>'array'], 'DateTimeImmutable::__wakeup' => ['void'], 'DateTimeImmutable::createFromInterface' => ['self', 'object' => 'DateTimeInterface'], -'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], +'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}|false'], 'DateTimeInterface::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeInterface::format' => ['string', 'format'=>'string'], 'DateTimeInterface::getOffset' => ['int'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 79545d9053f..6488f5be3ab 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1044,7 +1044,7 @@ 'DateTime::createFromFormat' => ['static|false', 'format'=>'string', 'time'=>'string', 'timezone='=>'?DateTimeZone'], 'DateTime::diff' => ['DateInterval|false', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTime::format' => ['string|false', 'format'=>'string'], - 'DateTime::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], + 'DateTime::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}|false'], 'DateTime::getOffset' => ['int'], 'DateTime::getTimestamp' => ['int|false'], 'DateTime::getTimezone' => ['DateTimeZone|false'], @@ -1057,7 +1057,7 @@ 'DateTime::sub' => ['static', 'interval'=>'DateInterval'], 'DateTimeImmutable::__set_state' => ['static', 'array'=>'array'], 'DateTimeImmutable::__wakeup' => ['void'], - 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], + 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}|false'], 'DateTimeInterface::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeInterface::format' => ['string', 'format'=>'string'], 'DateTimeInterface::getOffset' => ['int'], @@ -10254,7 +10254,7 @@ 'date_default_timezone_set' => ['bool', 'timezoneId'=>'string'], 'date_diff' => ['DateInterval|false', 'baseObject'=>'DateTimeInterface', 'targetObject'=>'DateTimeInterface', 'absolute='=>'bool'], 'date_format' => ['string|false', 'object'=>'DateTimeInterface', 'format'=>'string'], - 'date_get_last_errors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], + 'date_get_last_errors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}|false'], 'date_interval_create_from_date_string' => ['DateInterval', 'datetime'=>'string'], 'date_interval_format' => ['string', 'object'=>'DateInterval', 'format'=>'string'], 'date_isodate_set' => ['DateTime|false', 'object'=>'DateTime', 'year'=>'int', 'week'=>'int', 'dayOfWeek='=>'int|mixed'], From 32aedbac58be50bda6ecf53c73e63414818a7fd8 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 6 Sep 2022 11:05:40 +0200 Subject: [PATCH 097/178] Add dateTimeModify return type provider --- .../Provider/MethodReturnTypeProvider.php | 2 + .../DateTimeModifyReturnTypeProvider.php | 61 ++++++++++++ tests/DateTimeTest.php | 96 +++++++++++++++++++ tests/MethodCallTest.php | 2 +- 4 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php create mode 100644 tests/DateTimeTest.php diff --git a/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php b/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php index f29dc77d6cc..a4203e0d7e1 100644 --- a/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php @@ -7,6 +7,7 @@ use Psalm\CodeLocation; use Psalm\Context; use Psalm\Internal\Provider\ReturnTypeProvider\ClosureFromCallableReturnTypeProvider; +use Psalm\Internal\Provider\ReturnTypeProvider\DateTimeModifyReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\DomNodeAppendChild; use Psalm\Internal\Provider\ReturnTypeProvider\ImagickPixelColorReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\PdoStatementReturnTypeProvider; @@ -58,6 +59,7 @@ public function __construct() $this->registerClass(SimpleXmlElementAsXml::class); $this->registerClass(PdoStatementReturnTypeProvider::class); $this->registerClass(ClosureFromCallableReturnTypeProvider::class); + $this->registerClass(DateTimeModifyReturnTypeProvider::class); } /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php new file mode 100644 index 00000000000..4d474dda537 --- /dev/null +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php @@ -0,0 +1,61 @@ +getSource(); + $call_args = $event->getCallArgs(); + $method_name_lowercase = $event->getMethodNameLowercase(); + if ( + !$statements_source instanceof StatementsAnalyzer + || $method_name_lowercase !== 'modify' + || !isset($call_args[0]) + ) { + return null; + } + + $first_arg = $call_args[0]->value; + $first_arg_type = $statements_source->node_data->getType($first_arg); + if (!$first_arg_type) { + return null; + } + + $has_date_time = false; + $has_false = false; + foreach ($first_arg_type->getAtomicTypes() as $type_part) { + if (!$type_part instanceof TLiteralString) { + return null; + } + + if (@(new \DateTime())->modify($type_part->value) === false) { + $has_false = true; + } else { + $has_date_time = true; + } + } + + if ($has_false && !$has_date_time) { + return Type::getFalse(); + } + if ($has_date_time && !$has_false) { + return Type::parseString($event->getFqClasslikeName()); + } + + return null; + } +} diff --git a/tests/DateTimeTest.php b/tests/DateTimeTest.php new file mode 100644 index 00000000000..6eb3b572038 --- /dev/null +++ b/tests/DateTimeTest.php @@ -0,0 +1,96 @@ +,error_levels?:string[]}> + */ + public function providerValidCodeParse(): iterable + { + return [ + 'modify' => [ + 'modify(getString()); + $b = $dateTimeImmutable->modify(getString()); + ', + 'assertions' => [ + '$a' => 'DateTime|false', + '$b' => 'DateTimeImmutable|false', + ], + ], + 'modifyWithValidConstant' => [ + 'modify(getString()); + $b = $dateTimeImmutable->modify(getString()); + ', + 'assertions' => [ + '$a' => 'DateTime', + '$b' => 'DateTimeImmutable', + ], + ], + 'modifyWithInvalidConstant' => [ + 'modify(getString()); + $b = $dateTimeImmutable->modify(getString()); + ', + 'assertions' => [ + '$a' => 'false', + '$b' => 'false', + ], + ], + 'modifyWithBothConstant' => [ + 'modify(getString()); + $b = $dateTimeImmutable->modify(getString()); + ', + 'assertions' => [ + '$a' => 'DateTime|false', + '$b' => 'DateTimeImmutable|false', + ], + ], + ]; + } +} diff --git a/tests/MethodCallTest.php b/tests/MethodCallTest.php index f30f1115fdc..cb3103fdeb8 100644 --- a/tests/MethodCallTest.php +++ b/tests/MethodCallTest.php @@ -272,7 +272,7 @@ final class MyDate extends DateTimeImmutable {} $b = (new DateTimeImmutable())->modify("+3 hours");', 'assertions' => [ '$yesterday' => 'MyDate|false', - '$b' => 'DateTimeImmutable|false', + '$b' => 'DateTimeImmutable', ], ], 'magicCall' => [ From fec5c8ab03daaa99c2ece9054cd5ce0599fb5f4f Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 14 Sep 2022 00:55:32 +0200 Subject: [PATCH 098/178] Fix cs --- .../ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php index 4d474dda537..1677ba64f6f 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php @@ -2,6 +2,7 @@ namespace Psalm\Internal\Provider\ReturnTypeProvider; +use DateTime; use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Plugin\EventHandler\Event\MethodReturnTypeProviderEvent; use Psalm\Plugin\EventHandler\MethodReturnTypeProviderInterface; @@ -21,8 +22,7 @@ public static function getMethodReturnType(MethodReturnTypeProviderEvent $event) $statements_source = $event->getSource(); $call_args = $event->getCallArgs(); $method_name_lowercase = $event->getMethodNameLowercase(); - if ( - !$statements_source instanceof StatementsAnalyzer + if (!$statements_source instanceof StatementsAnalyzer || $method_name_lowercase !== 'modify' || !isset($call_args[0]) ) { @@ -42,7 +42,7 @@ public static function getMethodReturnType(MethodReturnTypeProviderEvent $event) return null; } - if (@(new \DateTime())->modify($type_part->value) === false) { + if (@(new DateTime())->modify($type_part->value) === false) { $has_false = true; } else { $has_date_time = true; From 7bc29a91eb6532d0dacdb0c9d3775bdd9ff6e799 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Sat, 30 Jul 2022 01:29:05 +0200 Subject: [PATCH 099/178] make superglobals more specific Update VariableFetchAnalyzer.php --- src/Psalm/Codebase.php | 2 +- .../Fetch/VariableFetchAnalyzer.php | 134 ++++++++++++++++-- .../Analyzer/Statements/GlobalAnalyzer.php | 6 +- .../Internal/Type/AssertionReconciler.php | 2 +- 4 files changed, 131 insertions(+), 13 deletions(-) diff --git a/src/Psalm/Codebase.php b/src/Psalm/Codebase.php index c74ba4637ae..dcf5c0b906a 100644 --- a/src/Psalm/Codebase.php +++ b/src/Psalm/Codebase.php @@ -1088,7 +1088,7 @@ public function getSymbolInformation(string $file_path, string $symbol): ?array } if (strpos($symbol, '$') === 0) { - $type = VariableFetchAnalyzer::getGlobalType($symbol); + $type = VariableFetchAnalyzer::getGlobalType($symbol, $this->analysis_php_version_id); if (!$type->isMixed()) { return ['type' => 'analysis_php_version_id); self::taintVariable($statements_analyzer, $var_name, $type, $stmt); @@ -522,7 +532,7 @@ public static function isSuperGlobal(string $var_id): bool ); } - public static function getGlobalType(string $var_id): Union + public static function getGlobalType(string $var_id, int $codebase_analysis_php_version_id): Union { $config = Config::getInstance(); @@ -531,26 +541,132 @@ public static function getGlobalType(string $var_id): Union } if ($var_id === '$argv') { + // only in CLI, null otherwise return new Union([ - new TArray([Type::getInt(), Type::getString()]), + new TNonEmptyList(Type::getString()), + new TNull() ]); } if ($var_id === '$argc') { - return Type::getInt(); + // only in CLI, null otherwise + return new Union([ + new TIntRange(1, null), + new TNull() + ]); + } + + if (!self::isSuperGlobal($var_id)) { + return Type::getMixed(); } if ($var_id === '$http_response_header') { return new Union([ - new TList(Type::getString()) + new TList(Type::getNonEmptyString()) + ]); + } + + if ($var_id === '$GLOBALS') { + return new Union([ + new TNonEmptyArray([ + Type::getNonEmptyString(), + Type::getMixed() + ]) ]); } - if (self::isSuperGlobal($var_id)) { - $type = Type::getArray(); - if ($var_id === '$_SESSION') { - $type->possibly_undefined = true; + if ($var_id === '$_COOKIE') { + $type = new TArray( + [ + Type::getNonEmptyString(), + Type::getString(), + ] + ); + + return new Union([$type]); + } + + if (in_array($var_id, array('$_GET', '$_POST', '$_REQUEST'), true)) { + $array_key = new Union([new TNonEmptyString(), new TInt()]); + $array = new TNonEmptyArray( + [ + $array_key, + new Union([ + new TString(), + new TArray([ + $array_key, + Type::getMixed() + ]) + ]) + ] + ); + + $type = new TArray( + [ + $array_key, + new Union([new TString(), $array]), + ] + ); + + return new Union([$type]); + } + + if ($var_id === '$_SERVER' || $var_id === '$_ENV') { + $type = new TArray( + [ + Type::getNonEmptyString(), + new Union([new TFloat(), new TIntRange(1, null), new TString(), new TNonEmptyList(Type::getString())]), + ] + ); + + return new Union([$type]); + } + + if ($var_id === '$_FILES') { + $values = [ + 'name' => new Union([ + new TString(), + new TNonEmptyList(Type::getString()), + ]), + 'type' => new Union([ + new TString(), + new TNonEmptyList(Type::getString()), + ]), + 'size' => new Union([ + new TInt(), + new TNonEmptyList(Type::getInt()), + ]), + 'tmp_name' => new Union([ + new TString(), + new TNonEmptyList(Type::getString()), + ]), + 'error' => new Union([ + new TInt(), + new TNonEmptyList(Type::getInt()), + ]), + ]; + + if ($codebase_analysis_php_version_id >= 81000) { + $values['full_path'] = new Union([ + new TString(), + new TNonEmptyList(Type::getString()), + ]); } + + $type = new TKeyedArray($values); + + return new Union([$type]); + } + + if ($var_id === '$_SESSION') { + // keys must be string + $type = new Union([ + new TArray([ + Type::getNonEmptyString(), + Type::getMixed(), + ]) + ]); + $type->possibly_undefined = true; return $type; } diff --git a/src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php index c716bc2bfbc..d4280bd2a6e 100644 --- a/src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php @@ -33,6 +33,7 @@ public static function analyze( ); } + $codebase = $statements_analyzer->getCodebase(); $source = $statements_analyzer->getSource(); $function_storage = $source instanceof FunctionLikeAnalyzer ? $source->getFunctionLikeStorage($statements_analyzer) @@ -44,7 +45,8 @@ public static function analyze( $var_id = '$' . $var->name; if ($var->name === 'argv' || $var->name === 'argc') { - $context->vars_in_scope[$var_id] = VariableFetchAnalyzer::getGlobalType($var_id); + $context->vars_in_scope[$var_id] = + VariableFetchAnalyzer::getGlobalType($var_id, $codebase->analysis_php_version_id); } elseif (isset($function_storage->global_types[$var_id])) { $context->vars_in_scope[$var_id] = clone $function_storage->global_types[$var_id]; $context->vars_possibly_in_scope[$var_id] = true; @@ -52,7 +54,7 @@ public static function analyze( $context->vars_in_scope[$var_id] = $global_context && $global_context->hasVariable($var_id) ? clone $global_context->vars_in_scope[$var_id] - : VariableFetchAnalyzer::getGlobalType($var_id); + : VariableFetchAnalyzer::getGlobalType($var_id, $codebase->analysis_php_version_id); $context->vars_possibly_in_scope[$var_id] = true; diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index d1611accfa8..e72954cbaf0 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -117,7 +117,7 @@ public static function reconcile( && is_string($key) && VariableFetchAnalyzer::isSuperGlobal($key) ) { - $existing_var_type = VariableFetchAnalyzer::getGlobalType($key); + $existing_var_type = VariableFetchAnalyzer::getGlobalType($key, $codebase->analysis_php_version_id); } if ($existing_var_type === null) { From 5c39e66b15f32f7c5ff579c5830de17cadeb4fc4 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Sat, 10 Sep 2022 13:06:17 +0200 Subject: [PATCH 100/178] fix tests --- docs/running_psalm/issues/MixedArgument.md | 2 +- docs/running_psalm/issues/MixedArrayAccess.md | 2 +- .../issues/MixedArrayAssignment.md | 2 +- docs/running_psalm/issues/MixedArrayOffset.md | 2 +- docs/running_psalm/issues/MixedAssignment.md | 8 ++++---- docs/running_psalm/issues/MixedClone.md | 2 +- .../running_psalm/issues/MixedFunctionCall.md | 2 +- .../issues/MixedInferredReturnType.md | 2 +- docs/running_psalm/issues/MixedOperand.md | 2 +- .../issues/MixedReturnStatement.md | 2 +- .../issues/MixedStringOffsetAssignment.md | 2 +- tests/ArrayAssignmentTest.php | 2 +- tests/ArrayFunctionCallTest.php | 4 ++-- tests/AssertAnnotationTest.php | 2 +- tests/FileUpdates/TemporaryUpdateTest.php | 12 ++++++------ tests/FunctionCallTest.php | 6 +++--- tests/Internal/CliUtilsTest.php | 2 +- tests/JsonOutputTest.php | 8 ++++---- tests/LanguageServer/SymbolLookupTest.php | 6 +++--- tests/ReturnTypeTest.php | 2 +- tests/TaintTest.php | 19 ++++++++++--------- tests/Template/ClassTemplateTest.php | 2 +- tests/Template/ConditionalReturnTypeTest.php | 2 +- tests/TypeReconciliation/EmptyTest.php | 2 +- .../src/FileWithErrors.php | 7 ++++++- tests/fixtures/expected_taint_graph.dot | 13 +++++++++---- 26 files changed, 64 insertions(+), 53 deletions(-) diff --git a/docs/running_psalm/issues/MixedArgument.md b/docs/running_psalm/issues/MixedArgument.md index 896f01efe43..a7f1f5a1642 100644 --- a/docs/running_psalm/issues/MixedArgument.md +++ b/docs/running_psalm/issues/MixedArgument.md @@ -6,5 +6,5 @@ Emitted when Psalm cannot determine the type of an argument [ ' 5, "b" => 12, "c" => null], function(?int $i) { - return $_GET["a"]; + return $GLOBALS["a"]; } );', 'error_message' => 'MixedArgumentTypeCoercion', diff --git a/tests/AssertAnnotationTest.php b/tests/AssertAnnotationTest.php index 22442e8e1c5..8dead02b7d6 100644 --- a/tests/AssertAnnotationTest.php +++ b/tests/AssertAnnotationTest.php @@ -513,7 +513,7 @@ function assertIntOrFoo($b) : void { } /** @psalm-suppress MixedAssignment */ - $a = $_GET["a"]; + $a = $GLOBALS["a"]; assertIntOrFoo($a); diff --git a/tests/FileUpdates/TemporaryUpdateTest.php b/tests/FileUpdates/TemporaryUpdateTest.php index 529b8f2ff07..cff307f3669 100644 --- a/tests/FileUpdates/TemporaryUpdateTest.php +++ b/tests/FileUpdates/TemporaryUpdateTest.php @@ -217,7 +217,7 @@ public function foo() { } public function bar() { - $a = $_GET["foo"]; + $a = $GLOBALS["foo"]; return $this->foo(); } }', @@ -232,7 +232,7 @@ public function foo() : int { } public function bar() { - $a = $_GET["foo"]; + $a = $GLOBALS["foo"]; return $this->foo(); } }', @@ -247,7 +247,7 @@ public function foo() : int { } public function bar() : int { - $a = $_GET["foo"]; + $a = $GLOBALS["foo"]; return $this->foo(); } }', @@ -268,7 +268,7 @@ public function foo() : int { } public function bar() : int { - $a = $_GET["foo"]; + $a = $GLOBALS["foo"]; return $this->foo(); } }', @@ -285,7 +285,7 @@ public function foo() : int { } public function bar() : int { - $a = $_GET["foo"]; + $a = $GLOBALS["foo"]; return $this->foo(); } }', @@ -303,7 +303,7 @@ public function foo() : int { } public function bar() : int { - $a = $_GET["foo"]; + $a = $GLOBALS["foo"]; return $this->foo(); } }', diff --git a/tests/FunctionCallTest.php b/tests/FunctionCallTest.php index be4f4c32b17..e57a1d432b9 100644 --- a/tests/FunctionCallTest.php +++ b/tests/FunctionCallTest.php @@ -128,7 +128,7 @@ function foo() { } 'noRedundantConditionAfterMixedOrEmptyArrayCountCheck' => [ ' [ '$a' => 'false|int', @@ -1481,7 +1481,7 @@ function test() : void { $y2 = date("Y", 10000); $F2 = date("F", 10000); /** @psalm-suppress MixedArgument */ - $F3 = date("F", $_GET["F3"]);', + $F3 = date("F", $GLOBALS["F3"]);', [ '$y' => 'numeric-string', '$m' => 'numeric-string', diff --git a/tests/Internal/CliUtilsTest.php b/tests/Internal/CliUtilsTest.php index f6f6ab0511e..a2bfe9cb6fc 100644 --- a/tests/Internal/CliUtilsTest.php +++ b/tests/Internal/CliUtilsTest.php @@ -19,7 +19,7 @@ class CliUtilsTest extends TestCase protected function setUp(): void { global $argv; - $this->argv = $argv; + $this->argv = $argv ?? []; } protected function tearDown(): void diff --git a/tests/JsonOutputTest.php b/tests/JsonOutputTest.php index f151cfc820e..975ac545fce 100644 --- a/tests/JsonOutputTest.php +++ b/tests/JsonOutputTest.php @@ -123,11 +123,11 @@ function fooFoo() { 'assertCancelsMixedAssignment' => [ ' 'Docblock-defined type int for $a is always int', + assert(is_string($a)); + if (is_string($a)) {}', + 'message' => 'Docblock-defined type string for $a is always string', 'line' => 4, - 'error' => 'is_int($a)', + 'error' => 'is_string($a)', ], ]; } diff --git a/tests/LanguageServer/SymbolLookupTest.php b/tests/LanguageServer/SymbolLookupTest.php index c5ab588f23f..be928a96dc2 100644 --- a/tests/LanguageServer/SymbolLookupTest.php +++ b/tests/LanguageServer/SymbolLookupTest.php @@ -78,7 +78,7 @@ function qux(int $a, int $b) : int { return $a + $b; } - $_SERVER;' + $_SESSION;' ); new FileAnalyzer($this->project_analyzer, 'somefile.php', 'somefile.php'); @@ -111,9 +111,9 @@ function qux(int $a, int $b) : int { $this->assertNotNull($information); $this->assertSame("getSymbolInformation('somefile.php', '$_SERVER'); + $information = $codebase->getSymbolInformation('somefile.php', '$_SESSION'); $this->assertNotNull($information); - $this->assertSame("", $information['type']); + $this->assertSame("", $information['type']); $information = $codebase->getSymbolInformation('somefile.php', '$my_global'); $this->assertNotNull($information); diff --git a/tests/ReturnTypeTest.php b/tests/ReturnTypeTest.php index 9c522ee9df1..5642079d89e 100644 --- a/tests/ReturnTypeTest.php +++ b/tests/ReturnTypeTest.php @@ -1213,7 +1213,7 @@ function fooFoo(): A { * @psalm-suppress UndefinedClass */ function fooFoo(): A { - return $_GET["a"]; + return $GLOBALS["a"]; } fooFoo()->bar();', diff --git a/tests/TaintTest.php b/tests/TaintTest.php index 302ca24348f..1c7620e4c06 100644 --- a/tests/TaintTest.php +++ b/tests/TaintTest.php @@ -458,13 +458,6 @@ public static function slugify(string $url) : string { echo $a[0]["b"];', ], - 'intUntainted' => [ - ' [ ' [ ' 'TaintedHtml', ], 'foreachArg' => [ diff --git a/tests/Template/ClassTemplateTest.php b/tests/Template/ClassTemplateTest.php index ee84dbce884..6b94c0f9db0 100644 --- a/tests/Template/ClassTemplateTest.php +++ b/tests/Template/ClassTemplateTest.php @@ -1425,7 +1425,7 @@ public function __construct(array $elements = []) } /** @psalm-suppress MixedArgument */ - $c = new ArrayCollection($_GET["a"]);', + $c = new ArrayCollection($GLOBALS["a"]);', [ '$c' => 'ArrayCollection', ], diff --git a/tests/Template/ConditionalReturnTypeTest.php b/tests/Template/ConditionalReturnTypeTest.php index f01263a2fdd..15e4e9ee41f 100644 --- a/tests/Template/ConditionalReturnTypeTest.php +++ b/tests/Template/ConditionalReturnTypeTest.php @@ -40,7 +40,7 @@ public function getAttribute(?string $name, string $default = "") $a = (new A)->getAttribute("colour", "red"); // typed as string $b = (new A)->getAttribute(null); // typed as array /** @psalm-suppress MixedArgument */ - $c = (new A)->getAttribute($_GET["foo"]); // typed as string|array', + $c = (new A)->getAttribute($GLOBALS["foo"]); // typed as string|array', [ '$a' => 'string', '$b' => 'array', diff --git a/tests/TypeReconciliation/EmptyTest.php b/tests/TypeReconciliation/EmptyTest.php index 1d92af7130d..873d75c5459 100644 --- a/tests/TypeReconciliation/EmptyTest.php +++ b/tests/TypeReconciliation/EmptyTest.php @@ -200,7 +200,7 @@ function foo(int $t) : void { ' "$_GET['abc']-src/FileWithErrors.php:345-349" - "$_GET['abc']-src/FileWithErrors.php:345-349" -> "coalesce-src/FileWithErrors.php:345-363" + "$_GET:src/FileWithErrors.php:413" -> "$_GET['abc']-src/FileWithErrors.php:413-417" + "$_GET:src/FileWithErrors.php:440" -> "$_GET['abc']-src/FileWithErrors.php:440-444" + "$_GET:src/FileWithErrors.php:456" -> "$_GET['abc']-src/FileWithErrors.php:456-460" + "$_GET['abc']-src/FileWithErrors.php:440-444" -> "call to is_string-src/FileWithErrors.php:440-451" + "$_GET['abc']-src/FileWithErrors.php:456-460" -> "call to echo-src/FileWithErrors.php:407-473" "$s-src/FileWithErrors.php:109-110" -> "variable-use" -> "acme\sampleproject\bar" "$s-src/FileWithErrors.php:162-163" -> "variable-use" -> "acme\sampleproject\baz" "$s-src/FileWithErrors.php:215-216" -> "variable-use" -> "acme\sampleproject\bat" @@ -10,6 +13,8 @@ digraph Taints { "acme\sampleproject\bat#1" -> "$s-src/FileWithErrors.php:215-216" "acme\sampleproject\baz#1" -> "$s-src/FileWithErrors.php:162-163" "acme\sampleproject\foo#1" -> "$s-src/FileWithErrors.php:57-58" - "call to echo-src/FileWithErrors.php:335-364" -> "echo#1-src/filewitherrors.php:330" - "coalesce-src/FileWithErrors.php:345-363" -> "call to echo-src/FileWithErrors.php:335-364" + "call to echo-src/FileWithErrors.php:335-367" -> "echo#1-src/filewitherrors.php:330" + "call to echo-src/FileWithErrors.php:407-473" -> "echo#1-src/filewitherrors.php:402" + "call to is_string-src/FileWithErrors.php:440-451" -> "is_string#1-src/filewitherrors.php:430" + "coalesce-src/FileWithErrors.php:345-366" -> "call to echo-src/FileWithErrors.php:335-367" } From a3cb10c085c6d217149209f632d5b146cf0da47d Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Sun, 11 Sep 2022 17:03:19 +0200 Subject: [PATCH 101/178] make $_SERVER more detailed --- .../Fetch/VariableFetchAnalyzer.php | 112 ++++++++++++++++-- 1 file changed, 104 insertions(+), 8 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php index 60ef3b9f287..8b3f8de7a7a 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php @@ -22,7 +22,6 @@ use Psalm\IssueBuffer; use Psalm\Type; use Psalm\Type\Atomic\TArray; -use Psalm\Type\Atomic\TFloat; use Psalm\Type\Atomic\TInt; use Psalm\Type\Atomic\TIntRange; use Psalm\Type\Atomic\TKeyedArray; @@ -612,14 +611,111 @@ public static function getGlobalType(string $var_id, int $codebase_analysis_php_ } if ($var_id === '$_SERVER' || $var_id === '$_ENV') { - $type = new TArray( - [ - Type::getNonEmptyString(), - new Union([new TFloat(), new TIntRange(1, null), new TString(), new TNonEmptyList(Type::getString())]), - ] - ); + $string_helper = Type::getString(); + $string_helper->possibly_undefined = true; - return new Union([$type]); + $non_empty_string_helper = Type::getNonEmptyString(); + $non_empty_string_helper->possibly_undefined = true; + + $argv_helper = new Union([ + new TNonEmptyList(Type::getString()) + ]); + $argv_helper->possibly_undefined = true; + + $argc_helper = new Union([ + new TIntRange(1, null) + ]); + $argc_helper->possibly_undefined = true; + + $request_time_helper = new Union([ + new TIntRange(time(), null) + ]); + $request_time_helper->possibly_undefined = true; + + $request_time_float_helper = Type::getFloat(); + $request_time_float_helper->possibly_undefined = true; + + $detailed_type = new TKeyedArray([ + // https://www.php.net/manual/en/reserved.variables.server.php + 'PHP_SELF' => $non_empty_string_helper, + 'argv' => $argv_helper, + 'argc' => $argc_helper, + 'GATEWAY_INTERFACE' => $non_empty_string_helper, + 'SERVER_ADDR' => $non_empty_string_helper, + 'SERVER_NAME' => $non_empty_string_helper, + 'SERVER_SOFTWARE' => $non_empty_string_helper, + 'SERVER_PROTOCOL' => $non_empty_string_helper, + 'REQUEST_METHOD' => $non_empty_string_helper, + 'REQUEST_TIME' => $request_time_helper, + 'REQUEST_TIME_FLOAT' => $request_time_float_helper, + 'QUERY_STRING' => $string_helper, + 'DOCUMENT_ROOT' => $non_empty_string_helper, + 'HTTP_ACCEPT' => $non_empty_string_helper, + 'HTTP_ACCEPT_CHARSET' => $non_empty_string_helper, + 'HTTP_ACCEPT_ENCODING' => $non_empty_string_helper, + 'HTTP_ACCEPT_LANGUAGE' => $non_empty_string_helper, + 'HTTP_CONNECTION' => $non_empty_string_helper, + 'HTTP_HOST' => $non_empty_string_helper, + 'HTTP_REFERER' => $non_empty_string_helper, + 'HTTP_USER_AGENT' => $non_empty_string_helper, + 'HTTPS' => $string_helper, + 'REMOTE_ADDR' => $non_empty_string_helper, + 'REMOTE_HOST' => $non_empty_string_helper, + 'REMOTE_PORT' => $string_helper, + 'REMOTE_USER' => $non_empty_string_helper, + 'REDIRECT_REMOTE_USER' => $non_empty_string_helper, + 'SCRIPT_FILENAME' => $non_empty_string_helper, + 'SERVER_ADMIN' => $non_empty_string_helper, + 'SERVER_PORT' => $non_empty_string_helper, + 'SERVER_SIGNATURE' => $non_empty_string_helper, + 'PATH_TRANSLATED' => $non_empty_string_helper, + 'SCRIPT_NAME' => $non_empty_string_helper, + 'REQUEST_URI' => $non_empty_string_helper, + 'PHP_AUTH_DIGEST' => $non_empty_string_helper, + 'PHP_AUTH_USER' => $non_empty_string_helper, + 'PHP_AUTH_PW' => $non_empty_string_helper, + 'AUTH_TYPE' => $non_empty_string_helper, + 'PATH_INFO' => $non_empty_string_helper, + 'ORIG_PATH_INFO' => $non_empty_string_helper, + // misc from RFC not included above already http://www.faqs.org/rfcs/rfc3875.html + 'CONTENT_LENGTH' => $string_helper, + 'CONTENT_TYPE' => $string_helper, + // common, misc stuff + 'FCGI_ROLE' => $non_empty_string_helper, + 'HOME' => $non_empty_string_helper, + 'HTTP_CACHE_CONTROL' => $non_empty_string_helper, + 'HTTP_COOKIE' => $non_empty_string_helper, + 'HTTP_PRIORITY' => $non_empty_string_helper, + 'PATH' => $non_empty_string_helper, + 'REDIRECT_STATUS' => $non_empty_string_helper, + 'REQUEST_SCHEME' => $non_empty_string_helper, + 'USER' => $non_empty_string_helper, + // common, misc headers + 'HTTP_UPGRADE_INSECURE_REQUESTS' => $non_empty_string_helper, + 'HTTP_X_FORWARDED_PROTO' => $non_empty_string_helper, + 'HTTP_CLIENT_IP' => $non_empty_string_helper, + 'HTTP_X_REAL_IP' => $non_empty_string_helper, + 'HTTP_X_FORWARDED_FOR' => $non_empty_string_helper, + 'HTTP_CF_CONNECTING_IP' => $non_empty_string_helper, + 'HTTP_CF_IPCOUNTRY' => $non_empty_string_helper, + 'HTTP_CF_VISITOR' => $non_empty_string_helper, + 'HTTP_CDN_LOOP' => $non_empty_string_helper, + // common, misc browser headers + 'HTTP_DNT' => $non_empty_string_helper, + 'HTTP_SEC_FETCH_DEST' => $non_empty_string_helper, + 'HTTP_SEC_FETCH_USER' => $non_empty_string_helper, + 'HTTP_SEC_FETCH_MODE' => $non_empty_string_helper, + 'HTTP_SEC_FETCH_SITE' => $non_empty_string_helper, + 'HTTP_SEC_CH_UA_PLATFORM' => $non_empty_string_helper, + 'HTTP_SEC_CH_UA_MOBILE' => $non_empty_string_helper, + 'HTTP_SEC_CH_UA' => $non_empty_string_helper, + ]); + + // generic case for all other elements + $detailed_type->previous_key_type = Type::getNonEmptyString(); + $detailed_type->previous_value_type = Type::getString(); + + return new Union([$detailed_type]); } if ($var_id === '$_FILES') { From b701c7074b97e8f2e347d9b847587be25bd6d716 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Sun, 11 Sep 2022 18:18:45 +0200 Subject: [PATCH 102/178] fix tests for detailed $_SERVER --- tests/AssertAnnotationTest.php | 6 +++--- tests/TypeReconciliation/ConditionalTest.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/AssertAnnotationTest.php b/tests/AssertAnnotationTest.php index 8dead02b7d6..9ecb98208c5 100644 --- a/tests/AssertAnnotationTest.php +++ b/tests/AssertAnnotationTest.php @@ -375,7 +375,7 @@ function isInvalidString(?string $myVar) : bool { echo "Ma chaine " . $myString; }', ], - 'assertServerVar' => [ + 'assertSessionVar' => [ ' [ diff --git a/tests/TypeReconciliation/ConditionalTest.php b/tests/TypeReconciliation/ConditionalTest.php index 3b7d0148bd5..fb1e10efa92 100644 --- a/tests/TypeReconciliation/ConditionalTest.php +++ b/tests/TypeReconciliation/ConditionalTest.php @@ -768,10 +768,10 @@ function d(?iterable $foo): void { } }', ], - 'isStringServerVar' => [ + 'isStringSessionVar' => [ ' [ From a2118c65d3ef06cb3cdf5b8b19d18a2f32048f0c Mon Sep 17 00:00:00 2001 From: hirokinoue <70567194+hirokinoue@users.noreply.github.com> Date: Sat, 17 Sep 2022 22:11:28 +0900 Subject: [PATCH 103/178] add test case reproducing issue #7428 --- .../TypeReconciliation/ArrayKeyExistsTest.php | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/TypeReconciliation/ArrayKeyExistsTest.php b/tests/TypeReconciliation/ArrayKeyExistsTest.php index 0f8bfe16498..6367f4eed5d 100644 --- a/tests/TypeReconciliation/ArrayKeyExistsTest.php +++ b/tests/TypeReconciliation/ArrayKeyExistsTest.php @@ -2,6 +2,8 @@ namespace Psalm\Tests\TypeReconciliation; +use Psalm\Config; +use Psalm\Context; use Psalm\Tests\TestCase; use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait; use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait; @@ -430,4 +432,31 @@ function go(array $options): void { ], ]; } + + public function testAllowPropertyFetchAsNeedle(): void + { + Config::getInstance()->ensure_array_int_offsets_exist = true; + + $this->addFile( + 'somefile.php', + ' $bar */ + $bar = []; + + if (array_key_exists($foo->status, $bar)) { + echo $bar[$foo->status]; + }' + ); + + $this->analyzeFile('somefile.php', new Context()); + } } From faf4e8ef84d6433b5994290bc488f042301561d9 Mon Sep 17 00:00:00 2001 From: hirokinoue <70567194+hirokinoue@users.noreply.github.com> Date: Sat, 17 Sep 2022 22:36:16 +0900 Subject: [PATCH 104/178] allow PropertyFetch node to behave like Variable node --- .../Analyzer/Statements/Expression/AssertionFinder.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php index 1603d1655dd..eb537d018e2 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php @@ -3685,7 +3685,9 @@ private static function getArrayKeyExistsAssertions( } else { $first_var_name = null; } - } elseif ($expr->getArgs()[0]->value instanceof PhpParser\Node\Expr\Variable + } elseif (($expr->getArgs()[0]->value instanceof PhpParser\Node\Expr\Variable + || $expr->getArgs()[0]->value instanceof PhpParser\Node\Expr\PropertyFetch + ) && $source instanceof StatementsAnalyzer && ($first_var_type = $source->node_data->getType($expr->getArgs()[0]->value)) ) { From 2f870776e24d078bb8e0cce38cdbdf33b6aa48a5 Mon Sep 17 00:00:00 2001 From: hirokinoue <70567194+hirokinoue@users.noreply.github.com> Date: Sat, 17 Sep 2022 23:02:25 +0900 Subject: [PATCH 105/178] add test case derived from issue #7428 --- .../TypeReconciliation/ArrayKeyExistsTest.php | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/TypeReconciliation/ArrayKeyExistsTest.php b/tests/TypeReconciliation/ArrayKeyExistsTest.php index 6367f4eed5d..90fa7d938a1 100644 --- a/tests/TypeReconciliation/ArrayKeyExistsTest.php +++ b/tests/TypeReconciliation/ArrayKeyExistsTest.php @@ -459,4 +459,29 @@ class Foo { $this->analyzeFile('somefile.php', new Context()); } + + public function testAllowStaticPropertyFetchAsNeedle(): void + { + Config::getInstance()->ensure_array_int_offsets_exist = true; + + $this->addFile( + 'somefile.php', + ' $bar */ + $bar = []; + + if (array_key_exists(Foo::$status, $bar)) { + echo $bar[Foo::$status]; + }' + ); + + $this->analyzeFile('somefile.php', new Context()); + } } From 2cf131fb45f8b211e4b11e148519ed390a5323a2 Mon Sep 17 00:00:00 2001 From: hirokinoue <70567194+hirokinoue@users.noreply.github.com> Date: Sat, 17 Sep 2022 23:08:29 +0900 Subject: [PATCH 106/178] allow StaticPropertyFetch node to behave like Variable node --- .../Internal/Analyzer/Statements/Expression/AssertionFinder.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php index eb537d018e2..1cd6ed62f41 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php @@ -3687,6 +3687,7 @@ private static function getArrayKeyExistsAssertions( } } elseif (($expr->getArgs()[0]->value instanceof PhpParser\Node\Expr\Variable || $expr->getArgs()[0]->value instanceof PhpParser\Node\Expr\PropertyFetch + || $expr->getArgs()[0]->value instanceof PhpParser\Node\Expr\StaticPropertyFetch ) && $source instanceof StatementsAnalyzer && ($first_var_type = $source->node_data->getType($expr->getArgs()[0]->value)) From 9071e877a8d240bdf3ac7b32ba4f3549d5f83f52 Mon Sep 17 00:00:00 2001 From: hirokinoue <70567194+hirokinoue@users.noreply.github.com> Date: Sun, 18 Sep 2022 01:15:09 +0900 Subject: [PATCH 107/178] fix according to psalm analysis --- .../Analyzer/Statements/Expression/AssertionFinder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php index 1cd6ed62f41..029d9422cf9 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php @@ -3653,8 +3653,8 @@ private static function getArrayKeyExistsAssertions( ) : null; - if ($array_root) { - if ($first_var_name === null && isset($expr->getArgs()[0])) { + if ($array_root && isset($expr->getArgs()[0])) { + if ($first_var_name === null) { $first_arg = $expr->getArgs()[0]; if ($first_arg->value instanceof PhpParser\Node\Scalar\String_) { From 602e26edd4acbc3b77ebd5bbf06a5f36c2692e24 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Sat, 17 Sep 2022 21:21:33 +0200 Subject: [PATCH 108/178] Fix array_column with object and column name null --- .../ArrayColumnReturnTypeProvider.php | 9 +- tests/ReturnTypeProvider/ArrayColumnTest.php | 92 +++++++++++++++++++ 2 files changed, 95 insertions(+), 6 deletions(-) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php index 89e68a1eaa4..b9d45b1c3ef 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php @@ -36,7 +36,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev return Type::getMixed(); } - $row_shape = null; + $row_type = $row_shape = null; $input_array_not_empty = false; // calculate row shape @@ -45,7 +45,6 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev && $first_arg_type->hasArray() ) { $input_array = $first_arg_type->getAtomicTypes()['array']; - $row_type = null; if ($input_array instanceof TKeyedArray) { $row_type = $input_array->getGenericArrayType()->type_params[1]; } elseif ($input_array instanceof TArray) { @@ -104,7 +103,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev } $result_key_type = Type::getArrayKey(); - $result_element_type = null; + $result_element_type = null !== $row_type && $value_column_name_is_null ? $row_type : null; $have_at_least_one_res = false; // calculate results if ($row_shape instanceof TKeyedArray) { @@ -116,9 +115,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev } //array_column skips undefined elements so resulting type is necessarily defined $result_element_type->possibly_undefined = false; - } elseif ($value_column_name_is_null) { - $result_element_type = new Union([$row_shape]); - } else { + } elseif (!$value_column_name_is_null) { $result_element_type = Type::getMixed(); } diff --git a/tests/ReturnTypeProvider/ArrayColumnTest.php b/tests/ReturnTypeProvider/ArrayColumnTest.php index 5b5cf4d2276..400dad77465 100644 --- a/tests/ReturnTypeProvider/ArrayColumnTest.php +++ b/tests/ReturnTypeProvider/ArrayColumnTest.php @@ -3,10 +3,12 @@ namespace Psalm\Tests\ReturnTypeProvider; use Psalm\Tests\TestCase; +use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait; use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait; class ArrayColumnTest extends TestCase { + use InvalidCodeAnalysisTestTrait; use ValidCodeAnalysisTestTrait; public function providerValidCodeParse(): iterable @@ -61,5 +63,95 @@ function f(array $shape): array { } ', ]; + + yield 'arrayColumnWithObjectsAndColumnNameNull' => [ + 'foo(); + } + ', + ]; + + yield 'arrayColumnWithIntersectionAndColumnNameNull' => [ + 'foo(); + $instance->bar(); + } + ', + ]; + + yield 'arrayColumnWithArrayAndColumnNameNull' => [ + ' "", "instance" => new C]], null, "name") as $array) { + $array["instance"]->foo(); + } + ', + ]; + + yield 'arrayColumnWithListOfObject' => [ + ' $instances */ + $instances = []; + foreach (array_column($instances, null, "name") as $instance) { + foo($instance); + } + ', + ]; + + yield 'arrayColumnWithListOfArrays' => [ + ' $arrays */ + $arrays = []; + foreach (array_column($arrays, null, "name") as $array) { + foo($array); + } + ', + ]; + } + + public function providerInvalidCodeParse(): iterable + { + yield 'arrayColumnWithArrayAndColumnNameNull' => [ + ' $arrays */ + $arrays = []; + foreach (array_column($arrays, null, "name") as $array) { + $array["instance"]->foo(); + } + ', + 'error_message' => 'MixedMethodCall', + ]; } } From ee16caf596a784af2dbac0decdeacf85a042eceb Mon Sep 17 00:00:00 2001 From: HypeMC Date: Sat, 17 Sep 2022 22:20:01 +0200 Subject: [PATCH 109/178] Make template constraints examples in docs consistent --- docs/annotating_code/templated_annotations.md | 2 +- docs/annotating_code/type_syntax/conditional_types.md | 4 ++-- docs/running_psalm/issues/InvalidTemplateParam.md | 2 +- docs/running_psalm/issues/NonInvariantDocblockPropertyType.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/annotating_code/templated_annotations.md b/docs/annotating_code/templated_annotations.md index 512a83ab17a..ed1d8054494 100644 --- a/docs/annotating_code/templated_annotations.md +++ b/docs/annotating_code/templated_annotations.md @@ -229,7 +229,7 @@ Templated types aren't limited to key-value pairs, and you can re-use templates ```php */ diff --git a/docs/annotating_code/type_syntax/conditional_types.md b/docs/annotating_code/type_syntax/conditional_types.md index 3d57dab3357..00dacca29a4 100644 --- a/docs/annotating_code/type_syntax/conditional_types.md +++ b/docs/annotating_code/type_syntax/conditional_types.md @@ -18,7 +18,7 @@ Let's suppose we want to make a userland implementation of PHP's numeric additio Date: Sat, 17 Sep 2022 23:46:19 +0200 Subject: [PATCH 110/178] Document the object with properties syntax --- .../type_syntax/atomic_types.md | 1 + .../type_syntax/object_types.md | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/docs/annotating_code/type_syntax/atomic_types.md b/docs/annotating_code/type_syntax/atomic_types.md index 5fa957be454..093dad1b0a7 100644 --- a/docs/annotating_code/type_syntax/atomic_types.md +++ b/docs/annotating_code/type_syntax/atomic_types.md @@ -21,6 +21,7 @@ Atomic types are the basic building block of all type information used in Psalm. ## [Object types](object_types.md) - [object](object_types.md) +- [object{foo: string}](object_types.md) - [Exception, Foo\MyClass and Foo\MyClass](object_types.md) - [Generator](object_types.md) diff --git a/docs/annotating_code/type_syntax/object_types.md b/docs/annotating_code/type_syntax/object_types.md index 00af6c79e82..18de65d71a3 100644 --- a/docs/annotating_code/type_syntax/object_types.md +++ b/docs/annotating_code/type_syntax/object_types.md @@ -2,6 +2,25 @@ `object`, `stdClass`, `Foo`, `Bar\Baz` etc. are examples of object types. These types are also valid types in PHP. +#### Object properties + +Psalm supports specifying the properties of an object and their expected types, e.g.: + +```php +/** @param object{foo: string} $obj */ +function takesObject(object $obj) : string { + return $obj->foo; +} + +takesObject((object) ["foo" => "hello"]); +``` + +Optional properties can be denoted by a trailing `?`, e.g.: + +```php +/** @param object{optional?: string} */ +``` + #### Generic object types Psalm supports using generic object types like `ArrayObject`. Any generic object should be typehinted with appropriate [`@template` tags](../templated_annotations.md). From eb93f692513274bcce179de265be7fbe03409efe Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Sat, 17 Sep 2022 19:37:32 +0200 Subject: [PATCH 111/178] Add null-type to several DOM-functions --- dictionaries/CallMap.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index bcdd10cdb39..98d47ea3a36 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -2105,7 +2105,7 @@ 'DOMDocument::createTextNode' => ['DOMText|false', 'content'=>'string'], 'DOMDocument::getElementById' => ['?DOMElement', 'elementid'=>'string'], 'DOMDocument::getElementsByTagName' => ['DOMNodeList', 'name'=>'string'], -'DOMDocument::getElementsByTagNameNS' => ['DOMNodeList', 'namespaceuri'=>'string', 'localname'=>'string'], +'DOMDocument::getElementsByTagNameNS' => ['DOMNodeList', 'namespaceuri'=>'string|null', 'localname'=>'string'], 'DOMDocument::importNode' => ['DOMNode|false', 'importednode'=>'DOMNode', 'deep='=>'bool'], 'DOMDocument::load' => ['DOMDocument|bool', 'filename'=>'string', 'options='=>'int'], 'DOMDocument::loadHTML' => ['bool', 'source'=>'string', 'options='=>'int'], @@ -2137,23 +2137,23 @@ 'DOMElement::get_elements_by_tagname' => ['array', 'name'=>'string'], 'DOMElement::getAttribute' => ['string', 'name'=>'string'], 'DOMElement::getAttributeNode' => ['DOMAttr', 'name'=>'string'], -'DOMElement::getAttributeNodeNS' => ['DOMAttr', 'namespaceuri'=>'string', 'localname'=>'string'], -'DOMElement::getAttributeNS' => ['string', 'namespaceuri'=>'string', 'localname'=>'string'], +'DOMElement::getAttributeNodeNS' => ['DOMAttr', 'namespaceuri'=>'string|null', 'localname'=>'string'], +'DOMElement::getAttributeNS' => ['string', 'namespaceuri'=>'string|null', 'localname'=>'string'], 'DOMElement::getElementsByTagName' => ['DOMNodeList', 'name'=>'string'], -'DOMElement::getElementsByTagNameNS' => ['DOMNodeList', 'namespaceuri'=>'string', 'localname'=>'string'], +'DOMElement::getElementsByTagNameNS' => ['DOMNodeList', 'namespaceuri'=>'string|null', 'localname'=>'string'], 'DOMElement::has_attribute' => ['bool', 'name'=>'string'], 'DOMElement::hasAttribute' => ['bool', 'name'=>'string'], -'DOMElement::hasAttributeNS' => ['bool', 'namespaceuri'=>'string', 'localname'=>'string'], +'DOMElement::hasAttributeNS' => ['bool', 'namespaceuri'=>'string|null', 'localname'=>'string'], 'DOMElement::remove_attribute' => ['bool', 'name'=>'string'], 'DOMElement::removeAttribute' => ['bool', 'name'=>'string'], 'DOMElement::removeAttributeNode' => ['bool', 'oldnode'=>'DOMAttr'], -'DOMElement::removeAttributeNS' => ['bool', 'namespaceuri'=>'string', 'localname'=>'string'], +'DOMElement::removeAttributeNS' => ['bool', 'namespaceuri'=>'string|null', 'localname'=>'string'], 'DOMElement::set_attribute' => ['DomAttribute', 'name'=>'string', 'value'=>'string'], 'DOMElement::set_attribute_node' => ['DomNode', 'attr'=>'DOMNode'], 'DOMElement::setAttribute' => ['DOMAttr|false', 'name'=>'string', 'value'=>'string'], 'DOMElement::setAttributeNode' => ['?DOMAttr', 'attr'=>'DOMAttr'], 'DOMElement::setAttributeNodeNS' => ['DOMAttr', 'attr'=>'DOMAttr'], -'DOMElement::setAttributeNS' => ['void', 'namespaceuri'=>'string', 'qualifiedname'=>'string', 'value'=>'string'], +'DOMElement::setAttributeNS' => ['void', 'namespaceuri'=>'string|null', 'qualifiedname'=>'string', 'value'=>'string'], 'DOMElement::setIdAttribute' => ['void', 'name'=>'string', 'isid'=>'bool'], 'DOMElement::setIdAttributeNode' => ['void', 'attr'=>'DOMAttr', 'isid'=>'bool'], 'DOMElement::setIdAttributeNS' => ['void', 'namespaceuri'=>'string', 'localname'=>'string', 'isid'=>'bool'], From 3b737480bdc051181229e23add085e36e93147e1 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Sun, 18 Sep 2022 11:34:40 +0200 Subject: [PATCH 112/178] Fix CallMap_historical --- dictionaries/CallMap_historical.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index b0fb9eebf43..c5b135785cc 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -936,7 +936,7 @@ 'DOMDocument::createTextNode' => ['DOMText|false', 'content'=>'string'], 'DOMDocument::getElementById' => ['?DOMElement', 'elementid'=>'string'], 'DOMDocument::getElementsByTagName' => ['DOMNodeList', 'name'=>'string'], - 'DOMDocument::getElementsByTagNameNS' => ['DOMNodeList', 'namespaceuri'=>'string', 'localname'=>'string'], + 'DOMDocument::getElementsByTagNameNS' => ['DOMNodeList', 'namespaceuri'=>'string|null', 'localname'=>'string'], 'DOMDocument::importNode' => ['DOMNode|false', 'importednode'=>'DOMNode', 'deep='=>'bool'], 'DOMDocument::load' => ['DOMDocument|bool', 'filename'=>'string', 'options='=>'int'], 'DOMDocument::loadHTML' => ['bool', 'source'=>'string', 'options='=>'int'], @@ -958,23 +958,23 @@ 'DOMDocumentFragment::appendXML' => ['bool', 'data'=>'string'], 'DOMElement::__construct' => ['void', 'name'=>'string', 'value='=>'string', 'uri='=>'string'], 'DOMElement::getAttribute' => ['string', 'name'=>'string'], - 'DOMElement::getAttributeNS' => ['string', 'namespaceuri'=>'string', 'localname'=>'string'], + 'DOMElement::getAttributeNS' => ['string', 'namespaceuri'=>'string|null', 'localname'=>'string'], 'DOMElement::getAttributeNode' => ['DOMAttr', 'name'=>'string'], - 'DOMElement::getAttributeNodeNS' => ['DOMAttr', 'namespaceuri'=>'string', 'localname'=>'string'], + 'DOMElement::getAttributeNodeNS' => ['DOMAttr', 'namespaceuri'=>'string|null', 'localname'=>'string'], 'DOMElement::getElementsByTagName' => ['DOMNodeList', 'name'=>'string'], - 'DOMElement::getElementsByTagNameNS' => ['DOMNodeList', 'namespaceuri'=>'string', 'localname'=>'string'], + 'DOMElement::getElementsByTagNameNS' => ['DOMNodeList', 'namespaceuri'=>'string|null', 'localname'=>'string'], 'DOMElement::get_attribute' => ['string', 'name'=>'string'], 'DOMElement::get_attribute_node' => ['DomAttribute', 'name'=>'string'], 'DOMElement::get_elements_by_tagname' => ['array', 'name'=>'string'], 'DOMElement::hasAttribute' => ['bool', 'name'=>'string'], - 'DOMElement::hasAttributeNS' => ['bool', 'namespaceuri'=>'string', 'localname'=>'string'], + 'DOMElement::hasAttributeNS' => ['bool', 'namespaceuri'=>'string|null', 'localname'=>'string'], 'DOMElement::has_attribute' => ['bool', 'name'=>'string'], 'DOMElement::removeAttribute' => ['bool', 'name'=>'string'], - 'DOMElement::removeAttributeNS' => ['bool', 'namespaceuri'=>'string', 'localname'=>'string'], + 'DOMElement::removeAttributeNS' => ['bool', 'namespaceuri'=>'string|null', 'localname'=>'string'], 'DOMElement::removeAttributeNode' => ['bool', 'oldnode'=>'DOMAttr'], 'DOMElement::remove_attribute' => ['bool', 'name'=>'string'], 'DOMElement::setAttribute' => ['DOMAttr|false', 'name'=>'string', 'value'=>'string'], - 'DOMElement::setAttributeNS' => ['void', 'namespaceuri'=>'string', 'qualifiedname'=>'string', 'value'=>'string'], + 'DOMElement::setAttributeNS' => ['void', 'namespaceuri'=>'string|null', 'qualifiedname'=>'string', 'value'=>'string'], 'DOMElement::setAttributeNode' => ['?DOMAttr', 'attr'=>'DOMAttr'], 'DOMElement::setAttributeNodeNS' => ['DOMAttr', 'attr'=>'DOMAttr'], 'DOMElement::setIdAttribute' => ['void', 'name'=>'string', 'isid'=>'bool'], From 2a315bef45033c13f149b431d3443725299937f8 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Sun, 18 Sep 2022 11:53:09 +0200 Subject: [PATCH 113/178] Fix some more --- dictionaries/CallMap.php | 4 ++-- dictionaries/CallMap_historical.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 98d47ea3a36..e16876d5ea3 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -2094,12 +2094,12 @@ 'DOMComment::__construct' => ['void', 'value='=>'string'], 'DOMDocument::__construct' => ['void', 'version='=>'string', 'encoding='=>'string'], 'DOMDocument::createAttribute' => ['DOMAttr|false', 'name'=>'string'], -'DOMDocument::createAttributeNS' => ['DOMAttr|false', 'namespaceuri'=>'string', 'qualifiedname'=>'string'], +'DOMDocument::createAttributeNS' => ['DOMAttr|false', 'namespaceuri'=>'string|null', 'qualifiedname'=>'string'], 'DOMDocument::createCDATASection' => ['DOMCDATASection|false', 'data'=>'string'], 'DOMDocument::createComment' => ['DOMComment|false', 'data'=>'string'], 'DOMDocument::createDocumentFragment' => ['DOMDocumentFragment|false'], 'DOMDocument::createElement' => ['DOMElement|false', 'name'=>'string', 'value='=>'string'], -'DOMDocument::createElementNS' => ['DOMElement|false', 'namespaceuri'=>'string', 'qualifiedname'=>'string', 'value='=>'string'], +'DOMDocument::createElementNS' => ['DOMElement|false', 'namespaceuri'=>'string|null', 'qualifiedname'=>'string', 'value='=>'string'], 'DOMDocument::createEntityReference' => ['DOMEntityReference|false', 'name'=>'string'], 'DOMDocument::createProcessingInstruction' => ['DOMProcessingInstruction|false', 'target'=>'string', 'data='=>'string'], 'DOMDocument::createTextNode' => ['DOMText|false', 'content'=>'string'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index c5b135785cc..997fc467be1 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -925,12 +925,12 @@ 'DOMComment::__construct' => ['void', 'value='=>'string'], 'DOMDocument::__construct' => ['void', 'version='=>'string', 'encoding='=>'string'], 'DOMDocument::createAttribute' => ['DOMAttr|false', 'name'=>'string'], - 'DOMDocument::createAttributeNS' => ['DOMAttr|false', 'namespaceuri'=>'string', 'qualifiedname'=>'string'], + 'DOMDocument::createAttributeNS' => ['DOMAttr|false', 'namespaceuri'=>'string|null', 'qualifiedname'=>'string'], 'DOMDocument::createCDATASection' => ['DOMCDATASection|false', 'data'=>'string'], 'DOMDocument::createComment' => ['DOMComment|false', 'data'=>'string'], 'DOMDocument::createDocumentFragment' => ['DOMDocumentFragment|false'], 'DOMDocument::createElement' => ['DOMElement|false', 'name'=>'string', 'value='=>'string'], - 'DOMDocument::createElementNS' => ['DOMElement|false', 'namespaceuri'=>'string', 'qualifiedname'=>'string', 'value='=>'string'], + 'DOMDocument::createElementNS' => ['DOMElement|false', 'namespaceuri'=>'string|null', 'qualifiedname'=>'string', 'value='=>'string'], 'DOMDocument::createEntityReference' => ['DOMEntityReference|false', 'name'=>'string'], 'DOMDocument::createProcessingInstruction' => ['DOMProcessingInstruction|false', 'target'=>'string', 'data='=>'string'], 'DOMDocument::createTextNode' => ['DOMText|false', 'content'=>'string'], From e2e6265ba18c13604ab8261f73f79ecf884decae Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Thu, 15 Sep 2022 19:49:36 +0200 Subject: [PATCH 114/178] ignore nullable issues for $argv/$argc --- .../Expression/Fetch/VariableFetchAnalyzer.php | 14 ++++++++++++-- tests/Internal/CliUtilsTest.php | 4 ++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php index 8b3f8de7a7a..303caa17ce1 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php @@ -541,18 +541,28 @@ public static function getGlobalType(string $var_id, int $codebase_analysis_php_ if ($var_id === '$argv') { // only in CLI, null otherwise - return new Union([ + $argv_nullable = new Union([ new TNonEmptyList(Type::getString()), new TNull() ]); + // use TNull explicitly instead of this + // as it will cause weird errors due to ignore_nullable_issues true + // e.g. InvalidPropertyAssignmentValue + // $this->argv 'list' cannot be assigned type 'non-empty-list' + // $argv_nullable->possibly_undefined = true; + $argv_nullable->ignore_nullable_issues = true; + return $argv_nullable; } if ($var_id === '$argc') { // only in CLI, null otherwise - return new Union([ + $argc_nullable = new Union([ new TIntRange(1, null), new TNull() ]); + // $argc_nullable->possibly_undefined = true; + $argc_nullable->ignore_nullable_issues = true; + return $argc_nullable; } if (!self::isSuperGlobal($var_id)) { diff --git a/tests/Internal/CliUtilsTest.php b/tests/Internal/CliUtilsTest.php index a2bfe9cb6fc..928eb0152f1 100644 --- a/tests/Internal/CliUtilsTest.php +++ b/tests/Internal/CliUtilsTest.php @@ -12,14 +12,14 @@ class CliUtilsTest extends TestCase { /** - * @var array + * @var list */ private $argv = []; protected function setUp(): void { global $argv; - $this->argv = $argv ?? []; + $this->argv = $argv; } protected function tearDown(): void From 44785fadfca997c4e47019c7aa413f3acdb004a3 Mon Sep 17 00:00:00 2001 From: dennis Date: Tue, 13 Sep 2022 11:28:26 +0200 Subject: [PATCH 115/178] Introduce test with code inside namespace --- .../ThrowsBlockAdditionTest.php | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/FileManipulation/ThrowsBlockAdditionTest.php b/tests/FileManipulation/ThrowsBlockAdditionTest.php index ae9eabf415d..cf192ee04e3 100644 --- a/tests/FileManipulation/ThrowsBlockAdditionTest.php +++ b/tests/FileManipulation/ThrowsBlockAdditionTest.php @@ -118,6 +118,30 @@ function foo(string $s): string { ['MissingThrowsDocblock'], true, ], + 'addThrowsAnnotationToFunctionInNamespace' => [ + ' Date: Tue, 13 Sep 2022 11:31:20 +0200 Subject: [PATCH 116/178] Ensure @throws annotations reference valid Exception classes --- .../Internal/FileManipulation/FunctionDocblockManipulator.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php b/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php index ca652333ea8..6cc3c594681 100644 --- a/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php +++ b/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php @@ -405,6 +405,7 @@ private function getDocblock(): string $inferredThrowsClause = array_reduce( $this->throwsExceptions, function (string $throwsClause, string $exception) { + $exception = '\\' . $exception; return $throwsClause === '' ? $exception : $throwsClause.'|'.$exception; }, '' From b88d2890e29155d56727d9bdb7c3d551da774440 Mon Sep 17 00:00:00 2001 From: dennis Date: Tue, 13 Sep 2022 11:32:19 +0200 Subject: [PATCH 117/178] Fix existing tests --- tests/FileManipulation/ThrowsBlockAdditionTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/FileManipulation/ThrowsBlockAdditionTest.php b/tests/FileManipulation/ThrowsBlockAdditionTest.php index cf192ee04e3..af4876ecd6f 100644 --- a/tests/FileManipulation/ThrowsBlockAdditionTest.php +++ b/tests/FileManipulation/ThrowsBlockAdditionTest.php @@ -20,7 +20,7 @@ function foo(string $s): string { }', ' Date: Sat, 17 Sep 2022 09:58:49 +0200 Subject: [PATCH 118/178] Add test to document cross namespace behaviour --- .../ThrowsBlockAdditionTest.php | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/FileManipulation/ThrowsBlockAdditionTest.php b/tests/FileManipulation/ThrowsBlockAdditionTest.php index af4876ecd6f..02cb974e86e 100644 --- a/tests/FileManipulation/ThrowsBlockAdditionTest.php +++ b/tests/FileManipulation/ThrowsBlockAdditionTest.php @@ -142,6 +142,44 @@ function foo(string $s): string { ['MissingThrowsDocblock'], true, ], + 'addThrowsAnnotationToFunctionFromFunctionFromOtherNamespace' => [ + ' Date: Sat, 17 Sep 2022 10:55:19 +0200 Subject: [PATCH 119/178] Improved class name generation for @throws annotation --- src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php | 9 ++++++++- .../FileManipulation/FunctionDocblockManipulator.php | 1 - tests/FileManipulation/ThrowsBlockAdditionTest.php | 8 ++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php index 385a954c946..c9c63238b8e 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php @@ -720,7 +720,14 @@ public function analyze( } if (!$is_expected) { - $missingThrowsDocblockErrors[] = $possibly_thrown_exception; + $missing_docblock_exception = new TNamedObject($possibly_thrown_exception); + $missingThrowsDocblockErrors[] = $missing_docblock_exception->toNamespacedString( + $this->source->getNamespace(), + $this->source->getAliasedClassesFlipped(), + $this->source->getFQCLN(), + true + ); + foreach ($codelocations as $codelocation) { // issues are suppressed in ThrowAnalyzer, CallAnalyzer, etc. IssueBuffer::maybeAdd( diff --git a/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php b/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php index 6cc3c594681..ca652333ea8 100644 --- a/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php +++ b/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php @@ -405,7 +405,6 @@ private function getDocblock(): string $inferredThrowsClause = array_reduce( $this->throwsExceptions, function (string $throwsClause, string $exception) { - $exception = '\\' . $exception; return $throwsClause === '' ? $exception : $throwsClause.'|'.$exception; }, '' diff --git a/tests/FileManipulation/ThrowsBlockAdditionTest.php b/tests/FileManipulation/ThrowsBlockAdditionTest.php index 02cb974e86e..b5a57c1924d 100644 --- a/tests/FileManipulation/ThrowsBlockAdditionTest.php +++ b/tests/FileManipulation/ThrowsBlockAdditionTest.php @@ -20,7 +20,7 @@ function foo(string $s): string { }', ' Date: Sat, 17 Sep 2022 10:56:29 +0200 Subject: [PATCH 120/178] Test to describe use statements being applied --- .../ThrowsBlockAdditionTest.php | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/FileManipulation/ThrowsBlockAdditionTest.php b/tests/FileManipulation/ThrowsBlockAdditionTest.php index b5a57c1924d..41f4e4ae2df 100644 --- a/tests/FileManipulation/ThrowsBlockAdditionTest.php +++ b/tests/FileManipulation/ThrowsBlockAdditionTest.php @@ -180,6 +180,46 @@ function bar(): void { ['MissingThrowsDocblock'], true, ], + 'addThrowsAnnotationAccountsForUseStatements' => [ + ' Date: Wed, 3 Aug 2022 21:43:37 +0200 Subject: [PATCH 121/178] fix type for (string) true --- .../Statements/Expression/CastAnalyzer.php | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php index 439ee3bf19e..b75176e2b41 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php @@ -23,6 +23,7 @@ use Psalm\Type\Atomic\Scalar; use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TBool; +use Psalm\Type\Atomic\TClosedResource; use Psalm\Type\Atomic\TFalse; use Psalm\Type\Atomic\TFloat; use Psalm\Type\Atomic\TInt; @@ -33,6 +34,9 @@ use Psalm\Type\Atomic\TLiteralString; use Psalm\Type\Atomic\TMixed; use Psalm\Type\Atomic\TNamedObject; +use Psalm\Type\Atomic\TNonEmptyArray; +use Psalm\Type\Atomic\TNonEmptyList; +use Psalm\Type\Atomic\TNonEmptyString; use Psalm\Type\Atomic\TNonspecificLiteralInt; use Psalm\Type\Atomic\TNonspecificLiteralString; use Psalm\Type\Atomic\TNull; @@ -42,6 +46,7 @@ use Psalm\Type\Atomic\TResource; use Psalm\Type\Atomic\TString; use Psalm\Type\Atomic\TTemplateParam; +use Psalm\Type\Atomic\TTrue; use Psalm\Type\Union; use function array_merge; @@ -352,8 +357,28 @@ public static function castStringAttempt( continue; } + if ($atomic_type instanceof TTrue + ) { + $valid_strings[] = new TLiteralString('1'); + continue; + } + + if ($atomic_type instanceof TBool + ) { + $valid_strings[] = new TLiteralString('1'); + $valid_strings[] = new TLiteralString(''); + continue; + } + + if ($atomic_type instanceof TClosedResource + || $atomic_type instanceof TResource + ) { + $castable_types[] = new TNonEmptyString(); + + continue; + } + if ($atomic_type instanceof TMixed - || $atomic_type instanceof TResource || $atomic_type instanceof Scalar ) { $castable_types[] = new TString(); From 3bec76acb8c89637d3c6c4817f6a9f18ec49f3e4 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Wed, 3 Aug 2022 22:15:41 +0200 Subject: [PATCH 122/178] fix invalid casts for int https://psalm.dev/r/0d284c6f48 --- .../Statements/Expression/CastAnalyzer.php | 272 ++++++++++++++++-- 1 file changed, 243 insertions(+), 29 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php index b75176e2b41..4d45367cad5 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php @@ -52,7 +52,6 @@ use function array_merge; use function array_pop; use function array_values; -use function count; use function get_class; class CastAnalyzer @@ -67,45 +66,27 @@ public static function analyze( return false; } - $as_int = true; - $valid_int_type = null; $maybe_type = $statements_analyzer->node_data->getType($stmt->expr); if ($maybe_type) { if ($maybe_type->isInt()) { - $valid_int_type = $maybe_type; if (!$maybe_type->from_calculation) { self::handleRedundantCast($maybe_type, $statements_analyzer, $stmt); } } - if (count($maybe_type->getAtomicTypes()) === 1 - && $maybe_type->getSingleAtomic() instanceof TBool) { - $as_int = false; - $type = new Union([ - new TLiteralInt(0), - new TLiteralInt(1), - ]); - - if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph - ) { - $type->parent_nodes = $maybe_type->parent_nodes; - } - - $statements_analyzer->node_data->setType($stmt, $type); - } + $type = self::castIntAttempt( + $statements_analyzer, + $context, + $maybe_type, + $stmt->expr, + true + ); + } else { + $type = Type::getInt(); } - if ($as_int) { - $type = $valid_int_type ?? Type::getInt(); - - if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph - ) { - $type->parent_nodes = $maybe_type->parent_nodes ?? []; - } - - $statements_analyzer->node_data->setType($stmt, $type); - } + $statements_analyzer->node_data->setType($stmt, $type); return true; } @@ -305,6 +286,239 @@ public static function analyze( return false; } + public static function castIntAttempt( + StatementsAnalyzer $statements_analyzer, + Context $context, + Union $stmt_type, + PhpParser\Node\Expr $stmt, + bool $explicit_cast = false + ): Union { + $codebase = $statements_analyzer->getCodebase(); + + $possibly_unwanted_cast = []; + $invalid_casts = []; + $valid_ints = []; + $castable_types = []; + + $atomic_types = $stmt_type->getAtomicTypes(); + + $parent_nodes = []; + + if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph) { + $parent_nodes = $stmt_type->parent_nodes; + } + + while ($atomic_types) { + $atomic_type = array_pop($atomic_types); + + if ($atomic_type instanceof TInt) { + $valid_ints[] = $atomic_type; + + continue; + } + + if ($atomic_type instanceof TFloat) { + if ($atomic_type instanceof TLiteralFloat) { + $valid_ints[] = new TLiteralInt((int) $atomic_type->value); + } else { + $castable_types[] = new TInt(); + } + + continue; + } + + if ($atomic_type instanceof TString) { + if ($atomic_type instanceof TLiteralString && (int) $atomic_type->value !== 0) { + $valid_ints[] = new TLiteralInt((int) $atomic_type->value); + } elseif ($atomic_type instanceof TNumericString) { + $castable_types[] = new TInt(); + } else { + // any normal string + $valid_ints[] = new TLiteralInt(0); + } + + continue; + } + + if ($atomic_type instanceof TNull || $atomic_type instanceof TFalse) { + $valid_ints[] = new TLiteralInt(0); + continue; + } + + if ($atomic_type instanceof TTrue) { + $valid_ints[] = new TLiteralInt(1); + continue; + } + + if ($atomic_type instanceof TBool) { + // do NOT use TIntRange here, as it will cause invalid behavior, e.g. bitwiseAssignment + $valid_ints[] = new TLiteralInt(0); + $valid_ints[] = new TLiteralInt(1); + continue; + } + + // could be invalid, but allow it, as it is allowed for TString below too + if ($atomic_type instanceof TMixed + || $atomic_type instanceof TClosedResource + || $atomic_type instanceof TResource + || $atomic_type instanceof Scalar + ) { + $castable_types[] = new TInt(); + + continue; + } + + if ($atomic_type instanceof TNamedObject + || $atomic_type instanceof TObjectWithProperties + ) { + $intersection_types = [$atomic_type]; + + if ($atomic_type->extra_types) { + $intersection_types = array_merge($intersection_types, $atomic_type->extra_types); + } + + foreach ($intersection_types as $intersection_type) { + if ($intersection_type instanceof TNamedObject) { + $intersection_method_id = new MethodIdentifier( + $intersection_type->value, + '__tostring' + ); + + if ($codebase->methods->methodExists( + $intersection_method_id, + $context->calling_method_id, + new CodeLocation($statements_analyzer->getSource(), $stmt) + )) { + $return_type = $codebase->methods->getMethodReturnType( + $intersection_method_id, + $self_class + ) ?? Type::getString(); + + $declaring_method_id = $codebase->methods->getDeclaringMethodId($intersection_method_id); + + MethodCallReturnTypeFetcher::taintMethodCallResult( + $statements_analyzer, + $return_type, + $stmt, + $stmt, + [], + $intersection_method_id, + $declaring_method_id, + $intersection_type->value . '::__toString', + $context + ); + + if ($statements_analyzer->data_flow_graph) { + $parent_nodes = array_merge($return_type->parent_nodes, $parent_nodes); + } + + foreach ($return_type->getAtomicTypes() as $sub_atomic_type) { + if ($sub_atomic_type instanceof TLiteralString + && (int) $sub_atomic_type->value !== 0 + ) { + $valid_ints[] = new TLiteralInt((int) $sub_atomic_type->value); + } elseif ($sub_atomic_type instanceof TNumericString) { + $castable_types[] = new TInt(); + } else { + $valid_ints[] = new TLiteralInt(0); + } + } + + continue 2; + } + } + + if ($intersection_type instanceof TObjectWithProperties + && isset($intersection_type->methods['__toString']) + ) { + $castable_types[] = new TInt(); + + continue 2; + } + } + } + + if ($atomic_type instanceof TNonEmptyArray + || $atomic_type instanceof TNonEmptyList + ) { + $possibly_unwanted_cast[] = $atomic_type->getId(); + + $valid_ints[] = new TLiteralInt(1); + + continue; + } + + if ($atomic_type instanceof TArray + || $atomic_type instanceof TList + || $atomic_type instanceof TKeyedArray + ) { + // if type is not specific, it can be both 0 or 1, depending on whether the array has data or not + // welcome to off-by-one hell if that happens :-) + $possibly_unwanted_cast[] = $atomic_type->getId(); + + $valid_ints[] = new TLiteralInt(0); + $valid_ints[] = new TLiteralInt(1); + + continue; + } + + if ($atomic_type instanceof TTemplateParam) { + $atomic_types = array_merge($atomic_types, $atomic_type->as->getAtomicTypes()); + + continue; + } + + $invalid_casts[] = $atomic_type->getId(); + } + + if ($invalid_casts) { + if ( $valid_ints || $castable_types ) { + IssueBuffer::maybeAdd( + new PossiblyInvalidCast( + $invalid_casts[0] . ' cannot be cast to int', + new CodeLocation( $statements_analyzer->getSource(), $stmt ) + ), + $statements_analyzer->getSuppressedIssues() + ); + } else { + IssueBuffer::maybeAdd( + new InvalidCast( + $invalid_casts[0] . ' cannot be cast to int', + new CodeLocation( $statements_analyzer->getSource(), $stmt ) + ), + $statements_analyzer->getSuppressedIssues() + ); + } + } elseif (!empty($possibly_unwanted_cast)) { + IssueBuffer::maybeAdd( + new PossiblyInvalidCast( + 'Casting ' . $possibly_unwanted_cast[0] . ' to int has possibly unintended value of 1', + new CodeLocation( $statements_analyzer->getSource(), $stmt ) + ), + $statements_analyzer->getSuppressedIssues() + ); + } elseif ($explicit_cast && !$castable_types) { + // todo: emit error here + } + + $valid_types = array_merge($valid_ints, $castable_types); + + if (!$valid_types) { + $int_type = Type::getInt(); + } else { + $int_type = TypeCombiner::combine( + $valid_types, + $codebase + ); + } + + if ($statements_analyzer->data_flow_graph) { + $int_type->parent_nodes = $parent_nodes; + } + + return $int_type; + } + public static function castStringAttempt( StatementsAnalyzer $statements_analyzer, Context $context, From 39ec75523e2aa7134b0e785cf99bb626df8b5ca8 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Wed, 3 Aug 2022 22:30:45 +0200 Subject: [PATCH 123/178] same for float --- .../Statements/Expression/CastAnalyzer.php | 247 +++++++++++++++++- 1 file changed, 241 insertions(+), 6 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php index 4d45367cad5..3d26e272e95 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php @@ -102,13 +102,16 @@ public static function analyze( if ($maybe_type->isFloat()) { self::handleRedundantCast($maybe_type, $statements_analyzer, $stmt); } - } - - $type = Type::getFloat(); - if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph - ) { - $type->parent_nodes = $maybe_type->parent_nodes ?? []; + $type = self::castFloatAttempt( + $statements_analyzer, + $context, + $maybe_type, + $stmt->expr, + true + ); + } else { + $type = Type::getFloat(); } $statements_analyzer->node_data->setType($stmt, $type); @@ -519,6 +522,238 @@ public static function castIntAttempt( return $int_type; } + public static function castFloatAttempt( + StatementsAnalyzer $statements_analyzer, + Context $context, + Union $stmt_type, + PhpParser\Node\Expr $stmt, + bool $explicit_cast = false + ): Union { + $codebase = $statements_analyzer->getCodebase(); + + $possibly_unwanted_cast = []; + $invalid_casts = []; + $valid_floats = []; + $castable_types = []; + + $atomic_types = $stmt_type->getAtomicTypes(); + + $parent_nodes = []; + + if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph) { + $parent_nodes = $stmt_type->parent_nodes; + } + + while ($atomic_types) { + $atomic_type = array_pop($atomic_types); + + if ($atomic_type instanceof TFloat) { + $valid_floats[] = $atomic_type; + + continue; + } + + if ($atomic_type instanceof TInt) { + if ($atomic_type instanceof TLiteralInt) { + $valid_floats[] = new TLiteralFloat((float) $atomic_type->value); + } else { + $castable_types[] = new TFloat(); + } + + continue; + } + + if ($atomic_type instanceof TString) { + if ($atomic_type instanceof TLiteralString && (float) $atomic_type->value !== 0.0) { + $valid_floats[] = new TLiteralFloat((float) $atomic_type->value); + } elseif ($atomic_type instanceof TNumericString) { + $castable_types[] = new TFloat(); + } else { + // any normal string + $valid_floats[] = new TLiteralFloat(0.0); + } + + continue; + } + + if ($atomic_type instanceof TNull || $atomic_type instanceof TFalse) { + $valid_floats[] = new TLiteralFloat(0.0); + continue; + } + + if ($atomic_type instanceof TTrue) { + $valid_floats[] = new TLiteralFloat(1.0); + continue; + } + + if ($atomic_type instanceof TBool) { + $valid_floats[] = new TLiteralFloat(0.0); + $valid_floats[] = new TLiteralFloat(1.0); + continue; + } + + // could be invalid, but allow it, as it is allowed for TString below too + if ($atomic_type instanceof TMixed + || $atomic_type instanceof TClosedResource + || $atomic_type instanceof TResource + || $atomic_type instanceof Scalar + ) { + $castable_types[] = new TFloat(); + + continue; + } + + if ($atomic_type instanceof TNamedObject + || $atomic_type instanceof TObjectWithProperties + ) { + $intersection_types = [$atomic_type]; + + if ($atomic_type->extra_types) { + $intersection_types = array_merge($intersection_types, $atomic_type->extra_types); + } + + foreach ($intersection_types as $intersection_type) { + if ($intersection_type instanceof TNamedObject) { + $intersection_method_id = new MethodIdentifier( + $intersection_type->value, + '__tostring' + ); + + if ($codebase->methods->methodExists( + $intersection_method_id, + $context->calling_method_id, + new CodeLocation($statements_analyzer->getSource(), $stmt) + )) { + $return_type = $codebase->methods->getMethodReturnType( + $intersection_method_id, + $self_class + ) ?? Type::getString(); + + $declaring_method_id = $codebase->methods->getDeclaringMethodId($intersection_method_id); + + MethodCallReturnTypeFetcher::taintMethodCallResult( + $statements_analyzer, + $return_type, + $stmt, + $stmt, + [], + $intersection_method_id, + $declaring_method_id, + $intersection_type->value . '::__toString', + $context + ); + + if ($statements_analyzer->data_flow_graph) { + $parent_nodes = array_merge($return_type->parent_nodes, $parent_nodes); + } + + foreach ($return_type->getAtomicTypes() as $sub_atomic_type) { + if ($sub_atomic_type instanceof TLiteralString + && (float) $sub_atomic_type->value !== 0.0 + ) { + $valid_floats[] = new TLiteralFloat((float) $sub_atomic_type->value); + } elseif ($sub_atomic_type instanceof TNumericString) { + $castable_types[] = new TFloat(); + } else { + $valid_floats[] = new TLiteralFloat(0.0); + } + } + + continue 2; + } + } + + if ($intersection_type instanceof TObjectWithProperties + && isset($intersection_type->methods['__toString']) + ) { + $castable_types[] = new TFloat(); + + continue 2; + } + } + } + + if ($atomic_type instanceof TNonEmptyArray + || $atomic_type instanceof TNonEmptyList + ) { + $possibly_unwanted_cast[] = $atomic_type->getId(); + + $valid_floats[] = new TLiteralFloat(1.0); + + continue; + } + + if ($atomic_type instanceof TArray + || $atomic_type instanceof TList + || $atomic_type instanceof TKeyedArray + ) { + // if type is not specific, it can be both 0 or 1, depending on whether the array has data or not + // welcome to off-by-one hell if that happens :-) + $possibly_unwanted_cast[] = $atomic_type->getId(); + + $valid_floats[] = new TLiteralFloat(0.0); + $valid_floats[] = new TLiteralFloat(1.0); + + continue; + } + + if ($atomic_type instanceof TTemplateParam) { + $atomic_types = array_merge($atomic_types, $atomic_type->as->getAtomicTypes()); + + continue; + } + + $invalid_casts[] = $atomic_type->getId(); + } + + if ($invalid_casts) { + if ( $valid_floats || $castable_types ) { + IssueBuffer::maybeAdd( + new PossiblyInvalidCast( + $invalid_casts[0] . ' cannot be cast to float', + new CodeLocation( $statements_analyzer->getSource(), $stmt ) + ), + $statements_analyzer->getSuppressedIssues() + ); + } else { + IssueBuffer::maybeAdd( + new InvalidCast( + $invalid_casts[0] . ' cannot be cast to float', + new CodeLocation( $statements_analyzer->getSource(), $stmt ) + ), + $statements_analyzer->getSuppressedIssues() + ); + } + } elseif (!empty($possibly_unwanted_cast)) { + IssueBuffer::maybeAdd( + new PossiblyInvalidCast( + 'Casting ' . $possibly_unwanted_cast[0] . ' to float has possibly unintended value of 1.0', + new CodeLocation( $statements_analyzer->getSource(), $stmt ) + ), + $statements_analyzer->getSuppressedIssues() + ); + } elseif ($explicit_cast && !$castable_types) { + // todo: emit error here + } + + $valid_types = array_merge($valid_floats, $castable_types); + + if (!$valid_types) { + $float_type = Type::getFloat(); + } else { + $float_type = TypeCombiner::combine( + $valid_types, + $codebase + ); + } + + if ($statements_analyzer->data_flow_graph) { + $float_type->parent_nodes = $parent_nodes; + } + + return $float_type; + } + public static function castStringAttempt( StatementsAnalyzer $statements_analyzer, Context $context, From d32efb0619dbc2ac277d3283a04c13f55c7a76e0 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Wed, 3 Aug 2022 23:48:49 +0200 Subject: [PATCH 124/178] float/int always 1 on "error", no PossiblyInvalidCasts by default --- .../Statements/Expression/CastAnalyzer.php | 58 +++++++------------ 1 file changed, 22 insertions(+), 36 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php index 3d26e272e95..f5af439823b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php @@ -471,32 +471,25 @@ public static function castIntAttempt( continue; } + // always 1 for "error" cases + $valid_ints[] = new TLiteralInt(1); + $invalid_casts[] = $atomic_type->getId(); } if ($invalid_casts) { - if ( $valid_ints || $castable_types ) { - IssueBuffer::maybeAdd( - new PossiblyInvalidCast( - $invalid_casts[0] . ' cannot be cast to int', - new CodeLocation( $statements_analyzer->getSource(), $stmt ) - ), - $statements_analyzer->getSuppressedIssues() - ); - } else { - IssueBuffer::maybeAdd( - new InvalidCast( - $invalid_casts[0] . ' cannot be cast to int', - new CodeLocation( $statements_analyzer->getSource(), $stmt ) - ), - $statements_analyzer->getSuppressedIssues() - ); - } + IssueBuffer::maybeAdd( + new InvalidCast( + $invalid_casts[0] . ' cannot be cast to int', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + ); } elseif (!empty($possibly_unwanted_cast)) { IssueBuffer::maybeAdd( new PossiblyInvalidCast( 'Casting ' . $possibly_unwanted_cast[0] . ' to int has possibly unintended value of 1', - new CodeLocation( $statements_analyzer->getSource(), $stmt ) + new CodeLocation($statements_analyzer->getSource(), $stmt) ), $statements_analyzer->getSuppressedIssues() ); @@ -703,32 +696,25 @@ public static function castFloatAttempt( continue; } + // always 1.0 for "error" cases + $valid_floats[] = new TLiteralFloat(1.0); + $invalid_casts[] = $atomic_type->getId(); } if ($invalid_casts) { - if ( $valid_floats || $castable_types ) { - IssueBuffer::maybeAdd( - new PossiblyInvalidCast( - $invalid_casts[0] . ' cannot be cast to float', - new CodeLocation( $statements_analyzer->getSource(), $stmt ) - ), - $statements_analyzer->getSuppressedIssues() - ); - } else { - IssueBuffer::maybeAdd( - new InvalidCast( - $invalid_casts[0] . ' cannot be cast to float', - new CodeLocation( $statements_analyzer->getSource(), $stmt ) - ), - $statements_analyzer->getSuppressedIssues() - ); - } + IssueBuffer::maybeAdd( + new InvalidCast( + $invalid_casts[0] . ' cannot be cast to float', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + ); } elseif (!empty($possibly_unwanted_cast)) { IssueBuffer::maybeAdd( new PossiblyInvalidCast( 'Casting ' . $possibly_unwanted_cast[0] . ' to float has possibly unintended value of 1.0', - new CodeLocation( $statements_analyzer->getSource(), $stmt ) + new CodeLocation($statements_analyzer->getSource(), $stmt) ), $statements_analyzer->getSuppressedIssues() ); From c3eebe25792c6a1a6b2ac6f23e1c258fa280ff36 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Thu, 4 Aug 2022 00:14:06 +0200 Subject: [PATCH 125/178] be less strict for generic string type --- .../Statements/Expression/CastAnalyzer.php | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php index f5af439823b..61c28c0a727 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php @@ -331,13 +331,14 @@ public static function castIntAttempt( } if ($atomic_type instanceof TString) { - if ($atomic_type instanceof TLiteralString && (int) $atomic_type->value !== 0) { + if ($atomic_type instanceof TLiteralString) { $valid_ints[] = new TLiteralInt((int) $atomic_type->value); } elseif ($atomic_type instanceof TNumericString) { $castable_types[] = new TInt(); } else { - // any normal string - $valid_ints[] = new TLiteralInt(0); + // any normal string is technically $valid_int[] = new TLiteralInt(0); + // however we cannot be certain that it's not inferred, therefore less strict + $castable_types[] = new TInt(); } continue; @@ -416,14 +417,14 @@ public static function castIntAttempt( } foreach ($return_type->getAtomicTypes() as $sub_atomic_type) { - if ($sub_atomic_type instanceof TLiteralString - && (int) $sub_atomic_type->value !== 0 - ) { + if ($sub_atomic_type instanceof TLiteralString) { $valid_ints[] = new TLiteralInt((int) $sub_atomic_type->value); } elseif ($sub_atomic_type instanceof TNumericString) { $castable_types[] = new TInt(); } else { - $valid_ints[] = new TLiteralInt(0); + // any normal string is technically $valid_int[] = new TLiteralInt(0); + // however we cannot be certain that it's not inferred, therefore less strict + $castable_types[] = new TInt(); } } @@ -557,13 +558,14 @@ public static function castFloatAttempt( } if ($atomic_type instanceof TString) { - if ($atomic_type instanceof TLiteralString && (float) $atomic_type->value !== 0.0) { + if ($atomic_type instanceof TLiteralString) { $valid_floats[] = new TLiteralFloat((float) $atomic_type->value); } elseif ($atomic_type instanceof TNumericString) { $castable_types[] = new TFloat(); } else { - // any normal string - $valid_floats[] = new TLiteralFloat(0.0); + // any normal string is technically $valid_floats[] = new TLiteralFloat(0.0); + // however we cannot be certain that it's not inferred, therefore less strict + $castable_types[] = new TFloat(); } continue; @@ -641,14 +643,14 @@ public static function castFloatAttempt( } foreach ($return_type->getAtomicTypes() as $sub_atomic_type) { - if ($sub_atomic_type instanceof TLiteralString - && (float) $sub_atomic_type->value !== 0.0 - ) { + if ($sub_atomic_type instanceof TLiteralString) { $valid_floats[] = new TLiteralFloat((float) $sub_atomic_type->value); } elseif ($sub_atomic_type instanceof TNumericString) { $castable_types[] = new TFloat(); } else { - $valid_floats[] = new TLiteralFloat(0.0); + // any normal string is technically $valid_int[] = new TLiteralInt(0); + // however we cannot be certain that it's not inferred, therefore less strict + $castable_types[] = new TFloat(); } } From 7cdad99645dd48dab1d639f97f89949e3155a02b Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Fri, 9 Sep 2022 03:04:14 +0200 Subject: [PATCH 126/178] add RiskyCast --- config.xsd | 1 + docs/running_psalm/error_levels.md | 1 + docs/running_psalm/issues.md | 1 + docs/running_psalm/issues/RiskyCast.md | 17 +++++++++++++ .../Statements/Expression/CastAnalyzer.php | 25 ++++++++++--------- src/Psalm/Issue/RiskyCast.php | 9 +++++++ 6 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 docs/running_psalm/issues/RiskyCast.md create mode 100644 src/Psalm/Issue/RiskyCast.php diff --git a/config.xsd b/config.xsd index c9a4cd88895..5a0dab58d68 100644 --- a/config.xsd +++ b/config.xsd @@ -462,6 +462,7 @@ + diff --git a/docs/running_psalm/error_levels.md b/docs/running_psalm/error_levels.md index c58cca026ff..38e628bfd93 100644 --- a/docs/running_psalm/error_levels.md +++ b/docs/running_psalm/error_levels.md @@ -187,6 +187,7 @@ These issues are treated as errors at level 3 and below. - [PossiblyUndefinedMethod](issues/PossiblyUndefinedMethod.md) - [PossiblyUndefinedVariable](issues/PossiblyUndefinedVariable.md) - [PropertyTypeCoercion](issues/PropertyTypeCoercion.md) + - [RiskyCast](issues/RiskyCast.md) ## Errors ignored at level 5 and higher diff --git a/docs/running_psalm/issues.md b/docs/running_psalm/issues.md index 50385f60e31..20ddd0eca76 100644 --- a/docs/running_psalm/issues.md +++ b/docs/running_psalm/issues.md @@ -214,6 +214,7 @@ - [RedundantPropertyInitializationCheck](issues/RedundantPropertyInitializationCheck.md) - [ReferenceConstraintViolation](issues/ReferenceConstraintViolation.md) - [ReservedWord](issues/ReservedWord.md) + - [RiskyCast](issues/RiskyCast.md) - [StringIncrement](issues/StringIncrement.md) - [TaintedCallable](issues/TaintedCallable.md) - [TaintedCookie](issues/TaintedCookie.md) diff --git a/docs/running_psalm/issues/RiskyCast.md b/docs/running_psalm/issues/RiskyCast.md new file mode 100644 index 00000000000..ef90d6e37b8 --- /dev/null +++ b/docs/running_psalm/issues/RiskyCast.md @@ -0,0 +1,17 @@ +# RiskyCast + +Emitted when attempting to cast an array to int or float + +```php +getCodebase(); - $possibly_unwanted_cast = []; + $risky_cast = []; $invalid_casts = []; $valid_ints = []; $castable_types = []; @@ -445,7 +446,7 @@ public static function castIntAttempt( if ($atomic_type instanceof TNonEmptyArray || $atomic_type instanceof TNonEmptyList ) { - $possibly_unwanted_cast[] = $atomic_type->getId(); + $risky_cast[] = $atomic_type->getId(); $valid_ints[] = new TLiteralInt(1); @@ -458,7 +459,7 @@ public static function castIntAttempt( ) { // if type is not specific, it can be both 0 or 1, depending on whether the array has data or not // welcome to off-by-one hell if that happens :-) - $possibly_unwanted_cast[] = $atomic_type->getId(); + $risky_cast[] = $atomic_type->getId(); $valid_ints[] = new TLiteralInt(0); $valid_ints[] = new TLiteralInt(1); @@ -486,10 +487,10 @@ public static function castIntAttempt( ), $statements_analyzer->getSuppressedIssues() ); - } elseif (!empty($possibly_unwanted_cast)) { + } elseif ($risky_cast) { IssueBuffer::maybeAdd( - new PossiblyInvalidCast( - 'Casting ' . $possibly_unwanted_cast[0] . ' to int has possibly unintended value of 1', + new RiskyCast( + 'Casting ' . $risky_cast[0] . ' to int has possibly unintended value of 0/1', new CodeLocation($statements_analyzer->getSource(), $stmt) ), $statements_analyzer->getSuppressedIssues() @@ -525,7 +526,7 @@ public static function castFloatAttempt( ): Union { $codebase = $statements_analyzer->getCodebase(); - $possibly_unwanted_cast = []; + $risky_cast = []; $invalid_casts = []; $valid_floats = []; $castable_types = []; @@ -671,7 +672,7 @@ public static function castFloatAttempt( if ($atomic_type instanceof TNonEmptyArray || $atomic_type instanceof TNonEmptyList ) { - $possibly_unwanted_cast[] = $atomic_type->getId(); + $risky_cast[] = $atomic_type->getId(); $valid_floats[] = new TLiteralFloat(1.0); @@ -684,7 +685,7 @@ public static function castFloatAttempt( ) { // if type is not specific, it can be both 0 or 1, depending on whether the array has data or not // welcome to off-by-one hell if that happens :-) - $possibly_unwanted_cast[] = $atomic_type->getId(); + $risky_cast[] = $atomic_type->getId(); $valid_floats[] = new TLiteralFloat(0.0); $valid_floats[] = new TLiteralFloat(1.0); @@ -712,10 +713,10 @@ public static function castFloatAttempt( ), $statements_analyzer->getSuppressedIssues() ); - } elseif (!empty($possibly_unwanted_cast)) { + } elseif ($risky_cast) { IssueBuffer::maybeAdd( - new PossiblyInvalidCast( - 'Casting ' . $possibly_unwanted_cast[0] . ' to float has possibly unintended value of 1.0', + new RiskyCast( + 'Casting ' . $risky_cast[0] . ' to float has possibly unintended value of 0.0/1.0', new CodeLocation($statements_analyzer->getSource(), $stmt) ), $statements_analyzer->getSuppressedIssues() diff --git a/src/Psalm/Issue/RiskyCast.php b/src/Psalm/Issue/RiskyCast.php new file mode 100644 index 00000000000..6576d430aeb --- /dev/null +++ b/src/Psalm/Issue/RiskyCast.php @@ -0,0 +1,9 @@ + Date: Fri, 9 Sep 2022 04:22:54 +0200 Subject: [PATCH 127/178] fix psalm-internal risky casts --- src/Psalm/Internal/Cli/LanguageServer.php | 3 ++- src/Psalm/Internal/Cli/Psalter.php | 3 ++- src/Psalm/Internal/Cli/Refactor.php | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/Cli/LanguageServer.php b/src/Psalm/Internal/Cli/LanguageServer.php index 634cdee16a4..1a0144def8c 100644 --- a/src/Psalm/Internal/Cli/LanguageServer.php +++ b/src/Psalm/Internal/Cli/LanguageServer.php @@ -32,6 +32,7 @@ use function in_array; use function ini_set; use function is_array; +use function is_numeric; use function is_string; use function preg_replace; use function realpath; @@ -298,7 +299,7 @@ function () use ($current_dir, $options, $vendor_dir): ?\Composer\Autoload\Class $find_unused_code = 'auto'; } - if (isset($options['disable-on-change'])) { + if (isset($options['disable-on-change']) && is_numeric($options['disable-on-change'])) { $project_analyzer->onchange_line_limit = (int) $options['disable-on-change']; } diff --git a/src/Psalm/Internal/Cli/Psalter.php b/src/Psalm/Internal/Cli/Psalter.php index 7db204c5f61..12e025fce7e 100644 --- a/src/Psalm/Internal/Cli/Psalter.php +++ b/src/Psalm/Internal/Cli/Psalter.php @@ -45,6 +45,7 @@ use function ini_set; use function is_array; use function is_dir; +use function is_numeric; use function is_string; use function microtime; use function pathinfo; @@ -230,7 +231,7 @@ function () use ($current_dir, $options, $vendor_dir): ?\Composer\Autoload\Class chdir($current_dir); } - $threads = isset($options['threads']) ? (int)$options['threads'] : 1; + $threads = isset($options['threads']) && is_numeric($options['threads']) ? (int)$options['threads'] : 1; if (isset($options['no-cache'])) { $providers = new Providers( diff --git a/src/Psalm/Internal/Cli/Refactor.php b/src/Psalm/Internal/Cli/Refactor.php index 864e9d4aebd..a3b5a114b37 100644 --- a/src/Psalm/Internal/Cli/Refactor.php +++ b/src/Psalm/Internal/Cli/Refactor.php @@ -35,6 +35,7 @@ use function in_array; use function ini_set; use function is_array; +use function is_numeric; use function is_string; use function max; use function microtime; @@ -284,7 +285,7 @@ function () use ($current_dir, $options, $vendor_dir): ?\Composer\Autoload\Class chdir($current_dir); } - $threads = isset($options['threads']) + $threads = isset($options['threads']) && is_numeric($options['threads']) ? (int)$options['threads'] : max(1, ProjectAnalyzer::getCpuCount() - 2); From bf1c0320fd7584aa6f287946e44b34f3f2c1872e Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Sun, 16 Jan 2022 21:33:04 +0100 Subject: [PATCH 128/178] Cherry-pick: Try to provide literal int types when possible (fixes #6966) (#7071) * Fixed vimeo/psalm#6966 * Only accept >= 0 values for mode argument in round() * Made round() only return float or literal float values and remove unneeded test * Registered RoundReturnTypeProvider * Updated cast analyzer to handle single string literal int values as literal ints * Fixed psalm errors * Fix invalid property accesses * Addressed comments * Added Tests * Marked RoundReturnTypeProvider as internal * Fixed CS --- dictionaries/CallMap.php | 2 +- dictionaries/CallMap_historical.php | 2 +- .../Provider/FunctionReturnTypeProvider.php | 2 + .../RoundReturnTypeProvider.php | 78 +++++++++++++++++++ tests/FunctionCallTest.php | 9 ++- tests/TypeReconciliation/ValueTest.php | 8 ++ 6 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index df6b3456d8a..df1739c6609 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -11714,7 +11714,7 @@ 'rewind' => ['bool', 'stream'=>'resource'], 'rewinddir' => ['null|false', 'dir_handle='=>'resource'], 'rmdir' => ['bool', 'directory'=>'string', 'context='=>'resource'], -'round' => ['float', 'num'=>'float', 'precision='=>'int', 'mode='=>'int'], +'round' => ['float', 'num'=>'float', 'precision='=>'int', 'mode='=>'0|positive-int'], 'rpm_close' => ['bool', 'rpmr'=>'resource'], 'rpm_get_tag' => ['mixed', 'rpmr'=>'resource', 'tagnum'=>'int'], 'rpm_is_valid' => ['bool', 'filename'=>'string'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 036c5c710c7..a8ad6acd48e 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -14751,7 +14751,7 @@ 'rewind' => ['bool', 'stream'=>'resource'], 'rewinddir' => ['null|false', 'dir_handle='=>'resource'], 'rmdir' => ['bool', 'directory'=>'string', 'context='=>'resource'], - 'round' => ['float', 'num'=>'float', 'precision='=>'int', 'mode='=>'int'], + 'round' => ['float', 'num'=>'float', 'precision='=>'int', 'mode='=>'0|positive-int'], 'rpm_close' => ['bool', 'rpmr'=>'resource'], 'rpm_get_tag' => ['mixed', 'rpmr'=>'resource', 'tagnum'=>'int'], 'rpm_is_valid' => ['bool', 'filename'=>'string'], diff --git a/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php b/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php index 8b124ea5532..e4636d5c3a0 100644 --- a/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php @@ -35,6 +35,7 @@ use Psalm\Internal\Provider\ReturnTypeProvider\MktimeReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\ParseUrlReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\RandReturnTypeProvider; +use Psalm\Internal\Provider\ReturnTypeProvider\RoundReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\StrReplaceReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\StrTrReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\TriggerErrorReturnTypeProvider; @@ -109,6 +110,7 @@ public function __construct() $this->registerClass(TriggerErrorReturnTypeProvider::class); $this->registerClass(RandReturnTypeProvider::class); $this->registerClass(InArrayReturnTypeProvider::class); + $this->registerClass(RoundReturnTypeProvider::class); } /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php new file mode 100644 index 00000000000..eb8ee561f54 --- /dev/null +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php @@ -0,0 +1,78 @@ + + */ + public static function getFunctionIds(): array + { + return ['round']; + } + + public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): ?Type\Union + { + $call_args = $event->getCallArgs(); + if (count($call_args) === 0) { + return null; + } + + $statements_source = $event->getStatementsSource(); + $nodeTypeProvider = $statements_source->getNodeTypeProvider(); + + $num_arg = $nodeTypeProvider->getType($call_args[0]->value); + + $precision_val = 0; + if ($statements_source instanceof StatementsAnalyzer && count($call_args) > 1) { + $type = $statements_source->node_data->getType($call_args[1]->value); + + if ($type !== null && $type->isSingle()) { + $atomic_type = array_values($type->getAtomicTypes())[0]; + if ($atomic_type instanceof Type\Atomic\TLiteralInt) { + $precision_val = $atomic_type->value; + } + } + } + + $mode_val = PHP_ROUND_HALF_UP; + if ($statements_source instanceof StatementsAnalyzer && count($call_args) > 2) { + $type = $statements_source->node_data->getType($call_args[2]->value); + + if ($type !== null && $type->isSingle()) { + $atomic_type = array_values($type->getAtomicTypes())[0]; + if ($atomic_type instanceof Type\Atomic\TLiteralInt) { + /** @var positive-int|0 $mode_val */ + $mode_val = $atomic_type->value; + } + } + } + + if ($num_arg !== null && $num_arg->isSingle()) { + $num_type = array_values($num_arg->getAtomicTypes())[0]; + if ($num_type instanceof Type\Atomic\TLiteralFloat || $num_type instanceof Type\Atomic\TLiteralInt) { + $rounded_val = round($num_type->value, $precision_val, $mode_val); + return new Type\Union([new Type\Atomic\TLiteralFloat($rounded_val)]); + } + } + + return new Type\Union([new Type\Atomic\TFloat()]); + } +} diff --git a/tests/FunctionCallTest.php b/tests/FunctionCallTest.php index e57a1d432b9..9b457ce600d 100644 --- a/tests/FunctionCallTest.php +++ b/tests/FunctionCallTest.php @@ -31,7 +31,6 @@ function filter(array $strings): array { } ' ], - 'typedArrayWithDefault' => [ ' 'lowercase-string', ], ], + 'round_literalValue' => [ + ' [ + '$a===' => 'float(10.36)', + ], + ], ]; } diff --git a/tests/TypeReconciliation/ValueTest.php b/tests/TypeReconciliation/ValueTest.php index b5963ac4125..cda14767394 100644 --- a/tests/TypeReconciliation/ValueTest.php +++ b/tests/TypeReconciliation/ValueTest.php @@ -909,6 +909,14 @@ function foo(string $s) : void { $interval = \DateInterval::createFromDateString("30 дней"); if ($interval === false) {}', ], + 'literalInt' => [ + ' [ + '$a===' => '5', + ], + ], ]; } From d69be4b9a271446f1295a0c89b528c5eafdf5a12 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Fri, 16 Sep 2022 14:19:26 +0200 Subject: [PATCH 129/178] objects even with __toString methods cannot be cast to int/float --- .../Statements/Expression/CastAnalyzer.php | 159 +++++------------- tests/ToStringTest.php | 12 ++ 2 files changed, 55 insertions(+), 116 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php index 039243fd00a..334a466e9bd 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php @@ -54,9 +54,18 @@ use function array_pop; use function array_values; use function get_class; +use function strtolower; class CastAnalyzer { + /** @var string[] */ + private const PSEUDO_CASTABLE_CLASSES = [ + 'SimpleXMLElement', + 'DOMNode', + 'GMP', + 'Decimal\Decimal', + ]; + public static function analyze( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Expr\Cast $stmt, @@ -78,7 +87,6 @@ public static function analyze( $type = self::castIntAttempt( $statements_analyzer, - $context, $maybe_type, $stmt->expr, true @@ -106,7 +114,6 @@ public static function analyze( $type = self::castFloatAttempt( $statements_analyzer, - $context, $maybe_type, $stmt->expr, true @@ -292,7 +299,6 @@ public static function analyze( public static function castIntAttempt( StatementsAnalyzer $statements_analyzer, - Context $context, Union $stmt_type, PhpParser\Node\Expr $stmt, bool $explicit_cast = false @@ -373,9 +379,7 @@ public static function castIntAttempt( continue; } - if ($atomic_type instanceof TNamedObject - || $atomic_type instanceof TObjectWithProperties - ) { + if ($atomic_type instanceof TNamedObject) { $intersection_types = [$atomic_type]; if ($atomic_type->extra_types) { @@ -383,62 +387,25 @@ public static function castIntAttempt( } foreach ($intersection_types as $intersection_type) { - if ($intersection_type instanceof TNamedObject) { - $intersection_method_id = new MethodIdentifier( - $intersection_type->value, - '__tostring' - ); - - if ($codebase->methods->methodExists( - $intersection_method_id, - $context->calling_method_id, - new CodeLocation($statements_analyzer->getSource(), $stmt) - )) { - $return_type = $codebase->methods->getMethodReturnType( - $intersection_method_id, - $self_class - ) ?? Type::getString(); - - $declaring_method_id = $codebase->methods->getDeclaringMethodId($intersection_method_id); - - MethodCallReturnTypeFetcher::taintMethodCallResult( - $statements_analyzer, - $return_type, - $stmt, - $stmt, - [], - $intersection_method_id, - $declaring_method_id, - $intersection_type->value . '::__toString', - $context - ); - - if ($statements_analyzer->data_flow_graph) { - $parent_nodes = array_merge($return_type->parent_nodes, $parent_nodes); - } - - foreach ($return_type->getAtomicTypes() as $sub_atomic_type) { - if ($sub_atomic_type instanceof TLiteralString) { - $valid_ints[] = new TLiteralInt((int) $sub_atomic_type->value); - } elseif ($sub_atomic_type instanceof TNumericString) { - $castable_types[] = new TInt(); - } else { - // any normal string is technically $valid_int[] = new TLiteralInt(0); - // however we cannot be certain that it's not inferred, therefore less strict - $castable_types[] = new TInt(); - } - } - - continue 2; - } + if (!$intersection_type instanceof TNamedObject) { + continue; } - if ($intersection_type instanceof TObjectWithProperties - && isset($intersection_type->methods['__toString']) - ) { - $castable_types[] = new TInt(); + // prevent "Could not get class storage for mixed" + if (!$codebase->classExists($intersection_type->value)) { + continue; + } - continue 2; + foreach (self::PSEUDO_CASTABLE_CLASSES as $pseudo_castable_class) { + if (strtolower($intersection_type->value) === strtolower($pseudo_castable_class) + || $codebase->classExtends( + $intersection_type->value, + $pseudo_castable_class + ) + ) { + $castable_types[] = new TInt(); + continue 3; + } } } } @@ -519,7 +486,6 @@ public static function castIntAttempt( public static function castFloatAttempt( StatementsAnalyzer $statements_analyzer, - Context $context, Union $stmt_type, PhpParser\Node\Expr $stmt, bool $explicit_cast = false @@ -599,9 +565,7 @@ public static function castFloatAttempt( continue; } - if ($atomic_type instanceof TNamedObject - || $atomic_type instanceof TObjectWithProperties - ) { + if ($atomic_type instanceof TNamedObject) { $intersection_types = [$atomic_type]; if ($atomic_type->extra_types) { @@ -609,62 +573,25 @@ public static function castFloatAttempt( } foreach ($intersection_types as $intersection_type) { - if ($intersection_type instanceof TNamedObject) { - $intersection_method_id = new MethodIdentifier( - $intersection_type->value, - '__tostring' - ); - - if ($codebase->methods->methodExists( - $intersection_method_id, - $context->calling_method_id, - new CodeLocation($statements_analyzer->getSource(), $stmt) - )) { - $return_type = $codebase->methods->getMethodReturnType( - $intersection_method_id, - $self_class - ) ?? Type::getString(); - - $declaring_method_id = $codebase->methods->getDeclaringMethodId($intersection_method_id); - - MethodCallReturnTypeFetcher::taintMethodCallResult( - $statements_analyzer, - $return_type, - $stmt, - $stmt, - [], - $intersection_method_id, - $declaring_method_id, - $intersection_type->value . '::__toString', - $context - ); - - if ($statements_analyzer->data_flow_graph) { - $parent_nodes = array_merge($return_type->parent_nodes, $parent_nodes); - } - - foreach ($return_type->getAtomicTypes() as $sub_atomic_type) { - if ($sub_atomic_type instanceof TLiteralString) { - $valid_floats[] = new TLiteralFloat((float) $sub_atomic_type->value); - } elseif ($sub_atomic_type instanceof TNumericString) { - $castable_types[] = new TFloat(); - } else { - // any normal string is technically $valid_int[] = new TLiteralInt(0); - // however we cannot be certain that it's not inferred, therefore less strict - $castable_types[] = new TFloat(); - } - } - - continue 2; - } + if (!$intersection_type instanceof TNamedObject) { + continue; } - if ($intersection_type instanceof TObjectWithProperties - && isset($intersection_type->methods['__toString']) - ) { - $castable_types[] = new TFloat(); + // prevent "Could not get class storage for mixed" + if (!$codebase->classExists($intersection_type->value)) { + continue; + } - continue 2; + foreach (self::PSEUDO_CASTABLE_CLASSES as $pseudo_castable_class) { + if (strtolower($intersection_type->value) === strtolower($pseudo_castable_class) + || $codebase->classExtends( + $intersection_type->value, + $pseudo_castable_class + ) + ) { + $castable_types[] = new TFloat(); + continue 3; + } } } } diff --git a/tests/ToStringTest.php b/tests/ToStringTest.php index 1a35aae8b73..40874ca4e94 100644 --- a/tests/ToStringTest.php +++ b/tests/ToStringTest.php @@ -505,6 +505,18 @@ public function __toString(): string ', 'error_message' => 'ImplicitToStringCast' ], + 'toStringTypecastNonString' => [ + ' 'InvalidCast', + ], ]; } } From d69e06242617d89a66bcc30843acdd30af1ce23c Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Tue, 20 Sep 2022 10:59:46 +0200 Subject: [PATCH 130/178] add RiskyCast tests --- tests/ToStringTest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/ToStringTest.php b/tests/ToStringTest.php index 40874ca4e94..f9101fa9cbb 100644 --- a/tests/ToStringTest.php +++ b/tests/ToStringTest.php @@ -517,6 +517,16 @@ function __toString(): string { echo (int) $foo;', 'error_message' => 'InvalidCast', ], + 'riskyArrayToIntCast' => [ + ' 'RiskyCast', + ], + 'riskyArrayToFloatCast' => [ + ' 'RiskyCast', + ], ]; } } From ce76158085afa6928b018bc559765784db59523e Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Tue, 16 Aug 2022 17:28:54 +0200 Subject: [PATCH 131/178] fix crash in ['bool', 'typelib_name'=>'string', 'case_insensitive='=>'true'], ], 'count' => [ - 'old' => ['int', 'value'=>'Countable|array|SimpleXMLElement|ResourceBundle', 'mode='=>'int'], + 'old' => ['int', 'value'=>'Countable|array|SimpleXMLElement', 'mode='=>'int'], 'new' => ['int', 'value'=>'Countable|array', 'mode='=>'int'], ], 'count_chars' => [ diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index a8ad6acd48e..0fc9a97fbd0 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -10085,7 +10085,7 @@ 'copy' => ['bool', 'from'=>'string', 'to'=>'string', 'context='=>'resource'], 'cos' => ['float', 'num'=>'float'], 'cosh' => ['float', 'num'=>'float'], - 'count' => ['int', 'value'=>'Countable|array|SimpleXMLElement|ResourceBundle', 'mode='=>'int'], + 'count' => ['int', 'value'=>'Countable|array|SimpleXMLElement', 'mode='=>'int'], 'count_chars' => ['array|false', 'input'=>'string', 'mode='=>'0|1|2'], 'count_chars\'1' => ['string|false', 'input'=>'string', 'mode='=>'3|4'], 'crack_check' => ['bool', 'dictionary'=>'', 'password'=>'string'], diff --git a/tests/Internal/Codebase/InternalCallMapHandlerTest.php b/tests/Internal/Codebase/InternalCallMapHandlerTest.php index f5be423c569..2f02ad18355 100644 --- a/tests/Internal/Codebase/InternalCallMapHandlerTest.php +++ b/tests/Internal/Codebase/InternalCallMapHandlerTest.php @@ -51,6 +51,7 @@ class InternalCallMapHandlerTest extends TestCase private static $ignoredFunctions = [ 'apcu_entry', 'array_multisort', + 'count', // #8346 'bcdiv', 'bcmod', 'bcpowmod', From 328561d3880c2e267b211ff307f82e3189e7a9fb Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Thu, 22 Sep 2022 00:41:06 +0200 Subject: [PATCH 132/178] add code for faster debugging next time --- tests/Internal/Codebase/InternalCallMapHandlerTest.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/Internal/Codebase/InternalCallMapHandlerTest.php b/tests/Internal/Codebase/InternalCallMapHandlerTest.php index 2f02ad18355..0821cd9cae5 100644 --- a/tests/Internal/Codebase/InternalCallMapHandlerTest.php +++ b/tests/Internal/Codebase/InternalCallMapHandlerTest.php @@ -51,7 +51,6 @@ class InternalCallMapHandlerTest extends TestCase private static $ignoredFunctions = [ 'apcu_entry', 'array_multisort', - 'count', // #8346 'bcdiv', 'bcmod', 'bcpowmod', @@ -520,7 +519,13 @@ public function testIgnoresAreSortedAndUnique(): void /** @var string */ $function = is_int($key) ? $value : $key; - $this->assertGreaterThan(0, strcmp($function, $previousFunction)); + $diff = strcmp($function, $previousFunction); + if ($diff <= 0) { + // faster debugging errors in tests + echo "\n" . $previousFunction . "\n" . $function . "\n"; + } + + $this->assertGreaterThan(0, $diff); $previousFunction = $function; } } From e803af4cd47f1bb368ba80d880c7b08d39d10651 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Thu, 22 Sep 2022 02:10:51 +0200 Subject: [PATCH 133/178] use cache for declared function when available before falling back to stubs fixes return type issues reported for the wrong file --- src/Psalm/Internal/Codebase/Functions.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Codebase/Functions.php b/src/Psalm/Internal/Codebase/Functions.php index ca882a0c6dd..a05723d923f 100644 --- a/src/Psalm/Internal/Codebase/Functions.php +++ b/src/Psalm/Internal/Codebase/Functions.php @@ -84,8 +84,9 @@ public function getStorage( $function_id = substr($function_id, 1); } + $from_stubs = false; if (isset(self::$stubbed_functions[$function_id])) { - return self::$stubbed_functions[$function_id]; + $from_stubs = self::$stubbed_functions[$function_id]; } $file_storage = null; @@ -117,6 +118,10 @@ public function getStorage( return $this->reflection->getFunctionStorage($function_id); } + if ($from_stubs) { + return $from_stubs; + } + throw new UnexpectedValueException( 'Expecting non-empty $root_file_path and $checked_file_path' ); @@ -135,6 +140,10 @@ public function getStorage( } } + if ($from_stubs) { + return $from_stubs; + } + throw new UnexpectedValueException( 'Expecting ' . $function_id . ' to have storage in ' . $checked_file_path ); @@ -145,6 +154,10 @@ public function getStorage( $declaring_file_storage = $this->file_storage_provider->get($declaring_file_path); if (!isset($declaring_file_storage->functions[$function_id])) { + if ($from_stubs) { + return $from_stubs; + } + throw new UnexpectedValueException( 'Not expecting ' . $function_id . ' to not have storage in ' . $declaring_file_path ); From b1c0c2d8a1a482491b6219cb6fd77b1e1de4c8d4 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Thu, 22 Sep 2022 00:10:27 +0200 Subject: [PATCH 134/178] add hideAllErrorsExceptPassedFiles config option for files only (not directories, since that wouldn't make practical sense) --- src/Psalm/Config.php | 13 +++++++++++++ src/Psalm/Internal/Analyzer/ProjectAnalyzer.php | 6 ++++++ 2 files changed, 19 insertions(+) diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index c97021c5ed3..72d7ae02b4c 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -295,6 +295,11 @@ class Config */ public $hide_external_errors = false; + /** + * @var bool + */ + public $hide_all_errors_except_passed_files = false; + /** @var bool */ public $allow_includes = true; @@ -926,6 +931,7 @@ private static function fromXmlAndPaths( 'useDocblockPropertyTypes' => 'use_docblock_property_types', 'throwExceptionOnError' => 'throw_exception', 'hideExternalErrors' => 'hide_external_errors', + 'hideAllErrorsExceptPassedFiles' => 'hide_all_errors_except_passed_files', 'resolveFromConfigFile' => 'resolve_from_config_file', 'allowFileIncludes' => 'allow_includes', 'strictBinaryOperands' => 'strict_binary_operands', @@ -1567,6 +1573,13 @@ public function reportIssueInFile(string $issue_type, string $file_path): bool $project_analyzer = ProjectAnalyzer::getInstance(); + // if the option is set and at least one file is passed via CLI + if ($this->hide_all_errors_except_passed_files + && $project_analyzer->check_paths_files + && !in_array($file_path, $project_analyzer->check_paths_files, true)) { + return false; + } + $codebase = $project_analyzer->getCodebase(); if (!$this->hide_external_errors) { diff --git a/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php b/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php index 40b3316df2a..21cc4ca0893 100644 --- a/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php @@ -204,6 +204,11 @@ class ProjectAnalyzer */ public $provide_completion = false; + /** + * @var list + */ + public $check_paths_files = []; + /** * @var array */ @@ -1178,6 +1183,7 @@ public function checkPaths(array $paths_to_check): void if (is_dir($path)) { $this->checkDirWithConfig($path, $this->config, true); } elseif (is_file($path)) { + $this->check_paths_files[] = $path; $this->codebase->addFilesToAnalyze([$path => $path]); $this->config->hide_external_errors = $this->config->isInProjectDirs($path); } From b68ac865e1ab2ed97d9e83471c3795b93b7d3c7f Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Sat, 24 Sep 2022 10:44:12 +0200 Subject: [PATCH 135/178] add docs --- docs/running_psalm/configuration.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/running_psalm/configuration.md b/docs/running_psalm/configuration.md index f24d4542cc8..6a7f4856bc2 100644 --- a/docs/running_psalm/configuration.md +++ b/docs/running_psalm/configuration.md @@ -400,6 +400,14 @@ Useful in testing, this makes Psalm throw a regular-old exception when it encoun ``` Whether or not to show issues in files that are used by your project files, but which are not included in ``. Defaults to `false`. +#### hideAllErrorsExceptPassedFiles +```xml + +``` +Whether or not to report issues only for files that were passed explicitly as arguments in CLI. This means any files that are loaded with require/include will not report either, if not set in CLI. Useful if you want to only check errors in a single or selected files. Defaults to `false`. + #### cacheDirectory ```xml Date: Tue, 4 Oct 2022 09:47:40 +0800 Subject: [PATCH 136/178] Add int type aliases based on existing codes --- src/Psalm/Type/Atomic.php | 24 +++++++++++++ src/Psalm/Type/Atomic/TNegativeInt .php | 41 +++++++++++++++++++++++ src/Psalm/Type/Atomic/TNonNegativeInt.php | 41 +++++++++++++++++++++++ src/Psalm/Type/Atomic/TNonPositiveInt.php | 41 +++++++++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 src/Psalm/Type/Atomic/TNegativeInt .php create mode 100644 src/Psalm/Type/Atomic/TNonNegativeInt.php create mode 100644 src/Psalm/Type/Atomic/TNonPositiveInt.php diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php index b0d456b65dd..47b54162c4b 100644 --- a/src/Psalm/Type/Atomic.php +++ b/src/Psalm/Type/Atomic.php @@ -62,6 +62,9 @@ use Psalm\Type\Atomic\TObject; use Psalm\Type\Atomic\TObjectWithProperties; use Psalm\Type\Atomic\TPositiveInt; +use Psalm\Type\Atomic\TNonPositiveInt; +use Psalm\Type\Atomic\TNegativeInt; +use Psalm\Type\Atomic\TNonNegativeInt; use Psalm\Type\Atomic\TResource; use Psalm\Type\Atomic\TScalar; use Psalm\Type\Atomic\TString; @@ -229,6 +232,15 @@ public static function create( case 'positive-int': return new TPositiveInt(); + + case 'non-positive-int': + return new TNonPositiveInt(); + + case 'negative-int': + return new TNegativeInt(); + + case 'non-negative-int': + return new TNonNegativeInt(); case 'numeric': return $php_version !== null ? new TNamedObject($value) : new TNumeric(); @@ -720,6 +732,18 @@ public function isTruthy(): bool return true; } + if ($this instanceof TNonPositiveInt) { + return true; + } + + if ($this instanceof TNegativeInt) { + return true; + } + + if ($this instanceof TNonNegativeInt) { + return true; + } + if ($this instanceof TLiteralClassString) { return true; } diff --git a/src/Psalm/Type/Atomic/TNegativeInt .php b/src/Psalm/Type/Atomic/TNegativeInt .php new file mode 100644 index 00000000000..1325492cdac --- /dev/null +++ b/src/Psalm/Type/Atomic/TNegativeInt .php @@ -0,0 +1,41 @@ + $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + return $use_phpdoc_format ? 'int' : 'negative-int'; + } +} diff --git a/src/Psalm/Type/Atomic/TNonNegativeInt.php b/src/Psalm/Type/Atomic/TNonNegativeInt.php new file mode 100644 index 00000000000..10c16d638a6 --- /dev/null +++ b/src/Psalm/Type/Atomic/TNonNegativeInt.php @@ -0,0 +1,41 @@ + 0) + * @deprecated will be removed in Psalm 5 + */ +class TNonNegativeInt extends TInt +{ + public function getId(bool $nested = false): string + { + return 'non-negative-int'; + } + + public function __toString(): string + { + return 'non-negative-int'; + } + + /** + * @return false + */ + public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + { + return false; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + return $use_phpdoc_format ? 'int' : 'non-negative-int'; + } +} diff --git a/src/Psalm/Type/Atomic/TNonPositiveInt.php b/src/Psalm/Type/Atomic/TNonPositiveInt.php new file mode 100644 index 00000000000..dbc66be77a4 --- /dev/null +++ b/src/Psalm/Type/Atomic/TNonPositiveInt.php @@ -0,0 +1,41 @@ + $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + return $use_phpdoc_format ? 'int' : 'non-positive-int'; + } +} From 6b6c320fe62e75db20fe41289e7fefd9880effe5 Mon Sep 17 00:00:00 2001 From: "William Owen O. Ponce" Date: Tue, 4 Oct 2022 09:53:22 +0800 Subject: [PATCH 137/178] Arrange use statements alphabetically --- src/Psalm/Type/Atomic.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php index 47b54162c4b..2672139322b 100644 --- a/src/Psalm/Type/Atomic.php +++ b/src/Psalm/Type/Atomic.php @@ -45,6 +45,7 @@ use Psalm\Type\Atomic\TLowercaseString; use Psalm\Type\Atomic\TMixed; use Psalm\Type\Atomic\TNamedObject; +use Psalm\Type\Atomic\TNegativeInt; use Psalm\Type\Atomic\TNever; use Psalm\Type\Atomic\TNonEmptyArray; use Psalm\Type\Atomic\TNonEmptyList; @@ -56,15 +57,14 @@ use Psalm\Type\Atomic\TNonFalsyString; use Psalm\Type\Atomic\TNonspecificLiteralInt; use Psalm\Type\Atomic\TNonspecificLiteralString; +use Psalm\Type\Atomic\TNonNegativeInt; +use Psalm\Type\Atomic\TNonPositiveInt; use Psalm\Type\Atomic\TNull; use Psalm\Type\Atomic\TNumeric; use Psalm\Type\Atomic\TNumericString; use Psalm\Type\Atomic\TObject; use Psalm\Type\Atomic\TObjectWithProperties; use Psalm\Type\Atomic\TPositiveInt; -use Psalm\Type\Atomic\TNonPositiveInt; -use Psalm\Type\Atomic\TNegativeInt; -use Psalm\Type\Atomic\TNonNegativeInt; use Psalm\Type\Atomic\TResource; use Psalm\Type\Atomic\TScalar; use Psalm\Type\Atomic\TString; From 04c9fe89c1225dd6dff975d20b6fea4ef3a173aa Mon Sep 17 00:00:00 2001 From: "William Owen O. Ponce" Date: Tue, 4 Oct 2022 09:59:34 +0800 Subject: [PATCH 138/178] Arrange use statements alphabetically again --- src/Psalm/Type/Atomic.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php index 2672139322b..657657bae41 100644 --- a/src/Psalm/Type/Atomic.php +++ b/src/Psalm/Type/Atomic.php @@ -55,10 +55,10 @@ use Psalm\Type\Atomic\TNonEmptyScalar; use Psalm\Type\Atomic\TNonEmptyString; use Psalm\Type\Atomic\TNonFalsyString; -use Psalm\Type\Atomic\TNonspecificLiteralInt; -use Psalm\Type\Atomic\TNonspecificLiteralString; use Psalm\Type\Atomic\TNonNegativeInt; use Psalm\Type\Atomic\TNonPositiveInt; +use Psalm\Type\Atomic\TNonspecificLiteralInt; +use Psalm\Type\Atomic\TNonspecificLiteralString; use Psalm\Type\Atomic\TNull; use Psalm\Type\Atomic\TNumeric; use Psalm\Type\Atomic\TNumericString; From ae426a00cec386680801c6973e27c8caa0d08003 Mon Sep 17 00:00:00 2001 From: "William Owen O. Ponce" Date: Wed, 5 Oct 2022 09:09:56 +0800 Subject: [PATCH 139/178] Remove irrelevant types, use keep aliases --- src/Psalm/Type/Atomic.php | 21 ++---------- src/Psalm/Type/Atomic/TNegativeInt .php | 41 ----------------------- src/Psalm/Type/Atomic/TNonNegativeInt.php | 41 ----------------------- src/Psalm/Type/Atomic/TNonPositiveInt.php | 41 ----------------------- 4 files changed, 3 insertions(+), 141 deletions(-) delete mode 100644 src/Psalm/Type/Atomic/TNegativeInt .php delete mode 100644 src/Psalm/Type/Atomic/TNonNegativeInt.php delete mode 100644 src/Psalm/Type/Atomic/TNonPositiveInt.php diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php index 657657bae41..7e5a258ea61 100644 --- a/src/Psalm/Type/Atomic.php +++ b/src/Psalm/Type/Atomic.php @@ -45,7 +45,6 @@ use Psalm\Type\Atomic\TLowercaseString; use Psalm\Type\Atomic\TMixed; use Psalm\Type\Atomic\TNamedObject; -use Psalm\Type\Atomic\TNegativeInt; use Psalm\Type\Atomic\TNever; use Psalm\Type\Atomic\TNonEmptyArray; use Psalm\Type\Atomic\TNonEmptyList; @@ -55,8 +54,6 @@ use Psalm\Type\Atomic\TNonEmptyScalar; use Psalm\Type\Atomic\TNonEmptyString; use Psalm\Type\Atomic\TNonFalsyString; -use Psalm\Type\Atomic\TNonNegativeInt; -use Psalm\Type\Atomic\TNonPositiveInt; use Psalm\Type\Atomic\TNonspecificLiteralInt; use Psalm\Type\Atomic\TNonspecificLiteralString; use Psalm\Type\Atomic\TNull; @@ -234,13 +231,13 @@ public static function create( return new TPositiveInt(); case 'non-positive-int': - return new TNonPositiveInt(); + return new TIntRange(null, -1); case 'negative-int': - return new TNegativeInt(); + return new TIntRange(null, -1); case 'non-negative-int': - return new TNonNegativeInt(); + return new TPositiveInt(); case 'numeric': return $php_version !== null ? new TNamedObject($value) : new TNumeric(); @@ -732,18 +729,6 @@ public function isTruthy(): bool return true; } - if ($this instanceof TNonPositiveInt) { - return true; - } - - if ($this instanceof TNegativeInt) { - return true; - } - - if ($this instanceof TNonNegativeInt) { - return true; - } - if ($this instanceof TLiteralClassString) { return true; } diff --git a/src/Psalm/Type/Atomic/TNegativeInt .php b/src/Psalm/Type/Atomic/TNegativeInt .php deleted file mode 100644 index 1325492cdac..00000000000 --- a/src/Psalm/Type/Atomic/TNegativeInt .php +++ /dev/null @@ -1,41 +0,0 @@ - $aliased_classes - * - */ - public function toNamespacedString( - ?string $namespace, - array $aliased_classes, - ?string $this_class, - bool $use_phpdoc_format - ): string { - return $use_phpdoc_format ? 'int' : 'negative-int'; - } -} diff --git a/src/Psalm/Type/Atomic/TNonNegativeInt.php b/src/Psalm/Type/Atomic/TNonNegativeInt.php deleted file mode 100644 index 10c16d638a6..00000000000 --- a/src/Psalm/Type/Atomic/TNonNegativeInt.php +++ /dev/null @@ -1,41 +0,0 @@ - 0) - * @deprecated will be removed in Psalm 5 - */ -class TNonNegativeInt extends TInt -{ - public function getId(bool $nested = false): string - { - return 'non-negative-int'; - } - - public function __toString(): string - { - return 'non-negative-int'; - } - - /** - * @return false - */ - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool - { - return false; - } - - /** - * @param array $aliased_classes - * - */ - public function toNamespacedString( - ?string $namespace, - array $aliased_classes, - ?string $this_class, - bool $use_phpdoc_format - ): string { - return $use_phpdoc_format ? 'int' : 'non-negative-int'; - } -} diff --git a/src/Psalm/Type/Atomic/TNonPositiveInt.php b/src/Psalm/Type/Atomic/TNonPositiveInt.php deleted file mode 100644 index dbc66be77a4..00000000000 --- a/src/Psalm/Type/Atomic/TNonPositiveInt.php +++ /dev/null @@ -1,41 +0,0 @@ - $aliased_classes - * - */ - public function toNamespacedString( - ?string $namespace, - array $aliased_classes, - ?string $this_class, - bool $use_phpdoc_format - ): string { - return $use_phpdoc_format ? 'int' : 'non-positive-int'; - } -} From 0c3a62bc4882dab8e057c58c9f720db891fd085b Mon Sep 17 00:00:00 2001 From: William Owen Ponce <31012084+hamburnyog@users.noreply.github.com> Date: Wed, 5 Oct 2022 14:29:24 +0800 Subject: [PATCH 140/178] Update args --- src/Psalm/Type/Atomic.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php index 7e5a258ea61..ff0868a23b5 100644 --- a/src/Psalm/Type/Atomic.php +++ b/src/Psalm/Type/Atomic.php @@ -231,13 +231,13 @@ public static function create( return new TPositiveInt(); case 'non-positive-int': - return new TIntRange(null, -1); + return new TIntRange(null, 0); case 'negative-int': return new TIntRange(null, -1); case 'non-negative-int': - return new TPositiveInt(); + return new TIntRange(0, null); case 'numeric': return $php_version !== null ? new TNamedObject($value) : new TNumeric(); From f1d1721fa7c3063a4a8bfd2ea1cfa3b8b758fe32 Mon Sep 17 00:00:00 2001 From: Peter de Blieck Date: Wed, 5 Oct 2022 14:36:33 +0200 Subject: [PATCH 141/178] Fixed function signatures of imap_delete and imap_undelete --- dictionaries/CallMap.php | 4 ++-- dictionaries/CallMap_81_delta.php | 4 ++-- dictionaries/CallMap_historical.php | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index df1739c6609..0f0f20cc941 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -6017,7 +6017,7 @@ 'imap_close' => ['bool', 'imap'=>'IMAP\Connection', 'flags='=>'int'], 'imap_create' => ['bool', 'imap'=>'IMAP\Connection', 'mailbox'=>'string'], 'imap_createmailbox' => ['bool', 'imap'=>'IMAP\Connection', 'mailbox'=>'string'], -'imap_delete' => ['bool', 'imap'=>'IMAP\Connection', 'message_num'=>'int', 'flags='=>'int'], +'imap_delete' => ['bool', 'imap'=>'IMAP\Connection', 'message_nums'=>'string', 'flags='=>'int'], 'imap_deletemailbox' => ['bool', 'imap'=>'IMAP\Connection', 'mailbox'=>'string'], 'imap_errors' => ['array|false'], 'imap_expunge' => ['bool', 'imap'=>'IMAP\Connection'], @@ -6074,7 +6074,7 @@ 'imap_thread' => ['array|false', 'imap'=>'IMAP\Connection', 'flags='=>'int'], 'imap_timeout' => ['int|bool', 'timeout_type'=>'int', 'timeout='=>'int'], 'imap_uid' => ['int|false', 'imap'=>'IMAP\Connection', 'message_num'=>'int'], -'imap_undelete' => ['bool', 'imap'=>'IMAP\Connection', 'message_num'=>'int', 'flags='=>'int'], +'imap_undelete' => ['bool', 'imap'=>'IMAP\Connection', 'message_nums'=>'string', 'flags='=>'int'], 'imap_unsubscribe' => ['bool', 'imap'=>'IMAP\Connection', 'mailbox'=>'string'], 'imap_utf7_decode' => ['string|false', 'string'=>'string'], 'imap_utf7_encode' => ['string', 'string'=>'string'], diff --git a/dictionaries/CallMap_81_delta.php b/dictionaries/CallMap_81_delta.php index 622c86614d0..2a1dffdc194 100644 --- a/dictionaries/CallMap_81_delta.php +++ b/dictionaries/CallMap_81_delta.php @@ -270,8 +270,8 @@ 'new' => ['bool', 'imap'=>'IMAP\Connection', 'mailbox'=>'string'], ], 'imap_delete' => [ - 'old' => ['bool', 'imap'=>'resource', 'message_num'=>'int', 'flags='=>'int'], - 'new' => ['bool', 'imap'=>'IMAP\Connection', 'message_num'=>'int', 'flags='=>'int'], + 'old' => ['bool', 'imap'=>'resource', 'message_nums'=>'string', 'flags='=>'int'], + 'new' => ['bool', 'imap'=>'IMAP\Connection', 'message_nums'=>'string', 'flags='=>'int'], ], 'imap_deletemailbox' => [ 'old' => ['bool', 'imap'=>'resource', 'mailbox'=>'string'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 0fc9a97fbd0..d67a99b4e82 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -12228,7 +12228,7 @@ 'imap_close' => ['bool', 'imap'=>'resource', 'flags='=>'int'], 'imap_create' => ['bool', 'imap'=>'resource', 'mailbox'=>'string'], 'imap_createmailbox' => ['bool', 'imap'=>'resource', 'mailbox'=>'string'], - 'imap_delete' => ['bool', 'imap'=>'resource', 'message_num'=>'int', 'flags='=>'int'], + 'imap_delete' => ['bool', 'imap'=>'resource', 'message_nums'=>'string', 'flags='=>'int'], 'imap_deletemailbox' => ['bool', 'imap'=>'resource', 'mailbox'=>'string'], 'imap_errors' => ['array|false'], 'imap_expunge' => ['bool', 'imap'=>'resource'], @@ -12285,7 +12285,7 @@ 'imap_thread' => ['array|false', 'imap'=>'resource', 'flags='=>'int'], 'imap_timeout' => ['int|bool', 'timeout_type'=>'int', 'timeout='=>'int'], 'imap_uid' => ['int|false', 'imap'=>'resource', 'message_num'=>'int'], - 'imap_undelete' => ['bool', 'imap'=>'resource', 'message_num'=>'int', 'flags='=>'int'], + 'imap_undelete' => ['bool', 'imap'=>'resource', 'message_nums'=>'string', 'flags='=>'int'], 'imap_unsubscribe' => ['bool', 'imap'=>'resource', 'mailbox'=>'string'], 'imap_utf7_decode' => ['string|false', 'string'=>'string'], 'imap_utf7_encode' => ['string', 'string'=>'string'], From ea5c2a1302b177595221600164eef31c05b63ed7 Mon Sep 17 00:00:00 2001 From: Peter de Blieck Date: Thu, 6 Oct 2022 08:52:36 +0200 Subject: [PATCH 142/178] Changed signature of imap_undelete in 8.1 delta --- dictionaries/CallMap_81_delta.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dictionaries/CallMap_81_delta.php b/dictionaries/CallMap_81_delta.php index 2a1dffdc194..d86cc48cf0e 100644 --- a/dictionaries/CallMap_81_delta.php +++ b/dictionaries/CallMap_81_delta.php @@ -450,8 +450,8 @@ 'new' => ['int|false', 'imap'=>'IMAP\Connection', 'message_num'=>'int'], ], 'imap_undelete' => [ - 'old' => ['bool', 'imap'=>'resource', 'message_num'=>'int', 'flags='=>'int'], - 'new' => ['bool', 'imap'=>'IMAP\Connection', 'message_num'=>'int', 'flags='=>'int'], + 'old' => ['bool', 'imap'=>'resource', 'message_nums'=>'string', 'flags='=>'int'], + 'new' => ['bool', 'imap'=>'IMAP\Connection', 'message_nums'=>'string', 'flags='=>'int'], ], 'imap_unsubscribe' => [ 'old' => ['bool', 'imap'=>'resource', 'mailbox'=>'string'], From 15453d4306b2be2202086b37905c46164846f5d1 Mon Sep 17 00:00:00 2001 From: Peter de Blieck Date: Thu, 6 Oct 2022 14:44:49 +0200 Subject: [PATCH 143/178] Removed imap_delete and imap_undelete from the ignoredFunctions list. --- tests/Internal/Codebase/InternalCallMapHandlerTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Internal/Codebase/InternalCallMapHandlerTest.php b/tests/Internal/Codebase/InternalCallMapHandlerTest.php index 0821cd9cae5..1fcac4e865b 100644 --- a/tests/Internal/Codebase/InternalCallMapHandlerTest.php +++ b/tests/Internal/Codebase/InternalCallMapHandlerTest.php @@ -119,11 +119,9 @@ class InternalCallMapHandlerTest extends TestCase 'imagettfbbox', 'imagettftext', 'imagexbm', - 'imap_delete', 'imap_open', 'imap_rfc822_write_address', 'imap_sort', - 'imap_undelete', 'inflate_add', 'inflate_get_read_len', 'inflate_get_status', From 41a6afda32b7f3bbedf7afce07e85801fdead603 Mon Sep 17 00:00:00 2001 From: Gregory Hargreaves Date: Fri, 7 Oct 2022 09:44:10 +0100 Subject: [PATCH 144/178] Add check for const with reserved word class --- src/Psalm/Internal/Analyzer/ClassAnalyzer.php | 10 ++++++++++ tests/ClassTest.php | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/Psalm/Internal/Analyzer/ClassAnalyzer.php b/src/Psalm/Internal/Analyzer/ClassAnalyzer.php index f55fbaaa90d..eca4f0f498b 100644 --- a/src/Psalm/Internal/Analyzer/ClassAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ClassAnalyzer.php @@ -508,6 +508,16 @@ public function analyze( $member_stmts[] = $stmt; foreach ($stmt->consts as $const) { + if ($const->name->toLowerString() === 'class') { + IssueBuffer::maybeAdd( + new ReservedWord( + 'A class constant cannot be named \'class\'', + new CodeLocation($this, $this->class), + $this->fq_class_name + ) + ); + } + $const_id = strtolower($this->fq_class_name) . '::' . $const->name; foreach ($codebase->class_constants_to_rename as $original_const_id => $new_const_name) { diff --git a/tests/ClassTest.php b/tests/ClassTest.php index 342fe78b0a3..55a82d3f76c 100644 --- a/tests/ClassTest.php +++ b/tests/ClassTest.php @@ -925,6 +925,18 @@ final class C implements I {} ', 'error_message' => 'InvalidTraversableImplementation', ], + 'cannotNameClassConstantClass' => [ + ' */ + protected const CLASS = Bar::class; + } + + class Bar {} + ', + 'error_message' => 'ReservedWord', + ] ]; } } From dec8d0edc2e94913a7abaee5e25aac29e9637dd3 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Sun, 9 Oct 2022 17:49:17 +0400 Subject: [PATCH 145/178] Mark hash functions as non-false See https://github.com/php/php-src/issues/7759 and PR https://github.com/phpstan/phpstan-src/pull/822/files# --- dictionaries/CallMap_80_delta.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dictionaries/CallMap_80_delta.php b/dictionaries/CallMap_80_delta.php index 4f659c915f3..3e2a9b17e5a 100644 --- a/dictionaries/CallMap_80_delta.php +++ b/dictionaries/CallMap_80_delta.php @@ -381,6 +381,14 @@ 'old' => ['string|false', 'format'=>'string', 'timestamp='=>'int'], 'new' => ['string|false', 'format'=>'string', 'timestamp='=>'?int'], ], + 'hash' => [ + 'old' => ['string|false', 'algo'=>'string', 'data'=>'string', 'binary='=>'bool'], + 'new' => ['non-empty-string', 'algo'=>'string', 'data'=>'string', 'binary='=>'bool', 'options='=>'array'], + ], + 'hash_hmac' => [ + 'old' => ['string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'binary='=>'bool'], + 'new' => ['non-empty-string', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'binary='=>'bool'], + ], 'hash_init' => [ 'old' => ['HashContext|false', 'algo'=>'string', 'flags='=>'int', 'key='=>'string'], 'new' => ['HashContext', 'algo'=>'string', 'flags='=>'int', 'key='=>'string'], From 3c3839b5cb81ceb5601da0cd1b9461502b428901 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Sun, 9 Oct 2022 18:01:27 +0400 Subject: [PATCH 146/178] Update hash(), hash_file() and hash_init() types see https://github.com/php/php-src/blob/php-8.1.5/ext/hash/hash.stub.php --- dictionaries/CallMap_81_delta.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dictionaries/CallMap_81_delta.php b/dictionaries/CallMap_81_delta.php index d86cc48cf0e..70aa34add06 100644 --- a/dictionaries/CallMap_81_delta.php +++ b/dictionaries/CallMap_81_delta.php @@ -222,16 +222,16 @@ 'new' => ['mixed|false', 'ftp' => 'FTP\Connection', 'option' => 'int'], ], 'hash' => [ - 'old' => ['string|false', 'algo'=>'string', 'data'=>'string', 'binary='=>'bool'], - 'new' => ['string|false', 'algo'=>'string', 'data'=>'string', 'binary='=>'bool', 'options='=>'array'], + 'old' => ['non-empty-string', 'algo'=>'string', 'data'=>'string', 'binary='=>'bool'], + 'new' => ['non-empty-string', 'algo'=>'string', 'data'=>'string', 'binary='=>'bool', 'options='=>'array{seed:scalar}'], ], 'hash_file' => [ 'old' => ['string|false', 'algo'=>'string', 'filename'=>'string', 'binary='=>'bool'], - 'new' => ['string|false', 'algo'=>'string', 'filename'=>'string', 'binary='=>'bool', 'options='=>'array'], + 'new' => ['string|false', 'algo'=>'string', 'filename'=>'string', 'binary='=>'bool', 'options='=>'array{seed:scalar}'], ], 'hash_init' => [ 'old' => ['HashContext', 'algo'=>'string', 'flags='=>'int', 'key='=>'string'], - 'new' => ['HashContext', 'algo'=>'string', 'flags='=>'int', 'key='=>'string', 'options='=>'array'], + 'new' => ['HashContext', 'algo'=>'string', 'flags='=>'int', 'key='=>'string', 'options='=>'array{seed:scalar}'], ], 'imageinterlace' => [ 'old' => ['int|bool', 'image'=>'GdImage', 'enable='=>'bool|null'], From ee86d6360c31d3c4cde4031151cd7755acf77047 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Sun, 9 Oct 2022 18:05:35 +0400 Subject: [PATCH 147/178] Remove extra changes --- dictionaries/CallMap_80_delta.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dictionaries/CallMap_80_delta.php b/dictionaries/CallMap_80_delta.php index 3e2a9b17e5a..aa5084ff451 100644 --- a/dictionaries/CallMap_80_delta.php +++ b/dictionaries/CallMap_80_delta.php @@ -383,7 +383,7 @@ ], 'hash' => [ 'old' => ['string|false', 'algo'=>'string', 'data'=>'string', 'binary='=>'bool'], - 'new' => ['non-empty-string', 'algo'=>'string', 'data'=>'string', 'binary='=>'bool', 'options='=>'array'], + 'new' => ['non-empty-string', 'algo'=>'string', 'data'=>'string', 'binary='=>'bool'], ], 'hash_hmac' => [ 'old' => ['string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'binary='=>'bool'], From 198a4ba9b0f8001a67e26763c2563f02f66effcc Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Sun, 9 Oct 2022 17:31:14 +0300 Subject: [PATCH 148/178] Return non-empty-string by hash functions --- dictionaries/CallMap.php | 16 ++++++++-------- dictionaries/CallMap_71_delta.php | 2 +- dictionaries/CallMap_72_delta.php | 4 ++-- dictionaries/CallMap_80_delta.php | 2 +- dictionaries/CallMap_81_delta.php | 4 ++-- dictionaries/CallMap_historical.php | 10 +++++----- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 0f0f20cc941..5284fd27ea7 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -4346,18 +4346,18 @@ 'HaruPage::stroke' => ['bool', 'close_path='=>'bool'], 'HaruPage::textOut' => ['bool', 'x'=>'float', 'y'=>'float', 'text'=>'string'], 'HaruPage::textRect' => ['bool', 'left'=>'float', 'top'=>'float', 'right'=>'float', 'bottom'=>'float', 'text'=>'string', 'align='=>'int'], -'hash' => ['string|false', 'algo'=>'string', 'data'=>'string', 'binary='=>'bool', 'options='=>'array'], +'hash' => ['non-empty-string', 'algo'=>'string', 'data'=>'string', 'binary='=>'bool', 'options='=>'array{seed:scalar}'], 'hash_algos' => ['list'], 'hash_copy' => ['HashContext', 'context'=>'HashContext'], 'hash_equals' => ['bool', 'known_string'=>'string', 'user_string'=>'string'], -'hash_file' => ['string|false', 'algo'=>'string', 'filename'=>'string', 'binary='=>'bool', 'options='=>'array'], -'hash_final' => ['string', 'context'=>'HashContext', 'binary='=>'bool'], -'hash_hkdf' => ['string|false', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], -'hash_hmac' => ['string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'binary='=>'bool'], +'hash_file' => ['non-empty-string|false', 'algo'=>'string', 'filename'=>'string', 'binary='=>'bool', 'options='=>'array{seed:scalar}'], +'hash_final' => ['non-empty-string', 'context'=>'HashContext', 'binary='=>'bool'], +'hash_hkdf' => ['non-empty-string', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], +'hash_hmac' => ['non-empty-string', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'binary='=>'bool'], 'hash_hmac_algos' => ['list'], -'hash_hmac_file' => ['string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'binary='=>'bool'], -'hash_init' => ['HashContext', 'algo'=>'string', 'flags='=>'int', 'key='=>'string', 'options='=>'array'], -'hash_pbkdf2' => ['string', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'binary='=>'bool'], +'hash_hmac_file' => ['non-empty-string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'binary='=>'bool'], +'hash_init' => ['HashContext', 'algo'=>'string', 'flags='=>'int', 'key='=>'string', 'options='=>'array{seed:scalar}'], +'hash_pbkdf2' => ['non-empty-string', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'binary='=>'bool'], 'hash_update' => ['bool', 'context'=>'HashContext', 'data'=>'string'], 'hash_update_file' => ['bool', 'context'=>'HashContext', 'filename'=>'string', 'stream_context='=>'?resource'], 'hash_update_stream' => ['int', 'context'=>'HashContext', 'stream'=>'resource', 'length='=>'int'], diff --git a/dictionaries/CallMap_71_delta.php b/dictionaries/CallMap_71_delta.php index 89451449402..2630ce958c1 100644 --- a/dictionaries/CallMap_71_delta.php +++ b/dictionaries/CallMap_71_delta.php @@ -21,7 +21,7 @@ 'curl_share_errno' => ['int|false', 'sh'=>'resource'], 'curl_share_strerror' => ['?string', 'error_code'=>'int'], 'getenv\'1' => ['array'], - 'hash_hkdf' => ['string|false', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], + 'hash_hkdf' => ['non-empty-string|false', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], 'is_iterable' => ['bool', 'value'=>'mixed'], 'openssl_get_curve_names' => ['list'], 'pcntl_async_signals' => ['bool', 'enable='=>'bool'], diff --git a/dictionaries/CallMap_72_delta.php b/dictionaries/CallMap_72_delta.php index fe0b3b249ed..9a7b5996ae7 100644 --- a/dictionaries/CallMap_72_delta.php +++ b/dictionaries/CallMap_72_delta.php @@ -141,8 +141,8 @@ 'new' => ['HashContext', 'context'=>'HashContext'], ], 'hash_final' => [ - 'old' => ['string', 'context'=>'resource', 'raw_output='=>'bool'], - 'new' => ['string', 'context'=>'HashContext', 'binary='=>'bool'], + 'old' => ['non-empty-string', 'context'=>'resource', 'raw_output='=>'bool'], + 'new' => ['non-empty-string', 'context'=>'HashContext', 'binary='=>'bool'], ], 'hash_init' => [ 'old' => ['resource', 'algo'=>'string', 'options='=>'int', 'key='=>'string'], diff --git a/dictionaries/CallMap_80_delta.php b/dictionaries/CallMap_80_delta.php index aa5084ff451..06308d48d2b 100644 --- a/dictionaries/CallMap_80_delta.php +++ b/dictionaries/CallMap_80_delta.php @@ -386,7 +386,7 @@ 'new' => ['non-empty-string', 'algo'=>'string', 'data'=>'string', 'binary='=>'bool'], ], 'hash_hmac' => [ - 'old' => ['string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'binary='=>'bool'], + 'old' => ['non-empty-string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'binary='=>'bool'], 'new' => ['non-empty-string', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'binary='=>'bool'], ], 'hash_init' => [ diff --git a/dictionaries/CallMap_81_delta.php b/dictionaries/CallMap_81_delta.php index 70aa34add06..5da78307225 100644 --- a/dictionaries/CallMap_81_delta.php +++ b/dictionaries/CallMap_81_delta.php @@ -226,8 +226,8 @@ 'new' => ['non-empty-string', 'algo'=>'string', 'data'=>'string', 'binary='=>'bool', 'options='=>'array{seed:scalar}'], ], 'hash_file' => [ - 'old' => ['string|false', 'algo'=>'string', 'filename'=>'string', 'binary='=>'bool'], - 'new' => ['string|false', 'algo'=>'string', 'filename'=>'string', 'binary='=>'bool', 'options='=>'array{seed:scalar}'], + 'old' => ['non-empty-string|false', 'algo'=>'string', 'filename'=>'string', 'binary='=>'bool'], + 'new' => ['non-empty-string|false', 'algo'=>'string', 'filename'=>'string', 'binary='=>'bool', 'options='=>'array{seed:scalar}'], ], 'hash_init' => [ 'old' => ['HashContext', 'algo'=>'string', 'flags='=>'int', 'key='=>'string'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index d67a99b4e82..21df4530590 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -11369,12 +11369,12 @@ 'hash_algos' => ['list'], 'hash_copy' => ['resource', 'context'=>'resource'], 'hash_equals' => ['bool', 'known_string'=>'string', 'user_string'=>'string'], - 'hash_file' => ['string|false', 'algo'=>'string', 'filename'=>'string', 'binary='=>'bool'], - 'hash_final' => ['string', 'context'=>'resource', 'raw_output='=>'bool'], - 'hash_hmac' => ['string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'binary='=>'bool'], - 'hash_hmac_file' => ['string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'binary='=>'bool'], + 'hash_file' => ['non-empty-string|false', 'algo'=>'string', 'filename'=>'string', 'binary='=>'bool'], + 'hash_final' => ['non-empty-string', 'context'=>'resource', 'raw_output='=>'bool'], + 'hash_hmac' => ['non-empty-string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'binary='=>'bool'], + 'hash_hmac_file' => ['non-empty-string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'binary='=>'bool'], 'hash_init' => ['resource', 'algo'=>'string', 'options='=>'int', 'key='=>'string'], - 'hash_pbkdf2' => ['string', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'binary='=>'bool'], + 'hash_pbkdf2' => ['non-empty-string', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'binary='=>'bool'], 'hash_update' => ['bool', 'context'=>'resource', 'data'=>'string'], 'hash_update_file' => ['bool', 'hcontext'=>'resource', 'filename'=>'string', 'scontext='=>'resource'], 'hash_update_stream' => ['int', 'context'=>'resource', 'handle'=>'resource', 'length='=>'int'], From 30dc46528deaeb1ec5b66d2f06ed836422e7709b Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Sun, 9 Oct 2022 17:40:36 +0300 Subject: [PATCH 149/178] Cleaup $ignoredFunctions --- tests/Internal/Codebase/InternalCallMapHandlerTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/Internal/Codebase/InternalCallMapHandlerTest.php b/tests/Internal/Codebase/InternalCallMapHandlerTest.php index 1fcac4e865b..16eb66e9a3a 100644 --- a/tests/Internal/Codebase/InternalCallMapHandlerTest.php +++ b/tests/Internal/Codebase/InternalCallMapHandlerTest.php @@ -371,9 +371,6 @@ class InternalCallMapHandlerTest extends TestCase 'gzeof', 'gzopen', 'gzpassthru', - 'hash', - 'hash_hkdf', - 'hash_hmac', 'iconv_get_encoding', 'igbinary_serialize', 'imagecolorclosest', From ea5f4cb5691cb0fad1917a9fd5ed0a8d6f5606d6 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Sun, 9 Oct 2022 17:40:52 +0300 Subject: [PATCH 150/178] Add PHP 8.0 delta for hash_hkdf() --- dictionaries/CallMap_80_delta.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dictionaries/CallMap_80_delta.php b/dictionaries/CallMap_80_delta.php index 06308d48d2b..a16d4967141 100644 --- a/dictionaries/CallMap_80_delta.php +++ b/dictionaries/CallMap_80_delta.php @@ -393,6 +393,10 @@ 'old' => ['HashContext|false', 'algo'=>'string', 'flags='=>'int', 'key='=>'string'], 'new' => ['HashContext', 'algo'=>'string', 'flags='=>'int', 'key='=>'string'], ], + 'hash_hkdf' => [ + 'old' => ['non-empty-string|false', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], + 'new' => ['non-empty-string', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], + ], 'hash_update_file' => [ 'old' => ['bool', 'context'=>'HashContext', 'filename'=>'string', 'stream_context='=>'resource'], 'new' => ['bool', 'context'=>'HashContext', 'filename'=>'string', 'stream_context='=>'?resource'], From dfa82366d723a1b5d42f52e9a8f8f96bc6409473 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Sun, 9 Oct 2022 16:44:34 +0200 Subject: [PATCH 151/178] add false return type to additional phpredis functions address https://github.com/phpredis/phpredis/pull/2120#issuecomment-1166644919 - weedwacker method, as I don't have time to check it all one by one --- stubs/phpredis.phpstub | 102 ++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/stubs/phpredis.phpstub b/stubs/phpredis.phpstub index 0687320eff0..8831306420f 100644 --- a/stubs/phpredis.phpstub +++ b/stubs/phpredis.phpstub @@ -31,7 +31,7 @@ class Redis { */ public function acl(string $subcmd, ...$args); - /** @return int|Redis */ + /** @return false|int|Redis */ public function append(string $key, mixed $value); public function auth(mixed $credentials): bool; @@ -40,15 +40,15 @@ class Redis { public function bgrewriteaof(): bool; - /** @return int|Redis */ + /** @return false|int|Redis */ public function bitcount(string $key, int $start = 0, int $end = -1); /** - * @return int|Redis + * @return false|int|Redis */ public function bitop(string $operation, string $deskey, string $srckey, string ...$other_keys): int; - /** @return int|Redis */ + /** @return false|int|Redis */ public function bitpos(string $key, int $bit, int $start = 0, int $end = -1); public function blPop(string|array $key, string|int $timeout_or_key, mixed ...$extra_args): array|null|false; @@ -57,9 +57,9 @@ class Redis { public function brpoplpush(string $src, string $dst, int $timeout): Redis|string|false; - public function bzPopMax(string|array $key, string|int $timeout_or_key, mixed ...$extra_args): array; + public function bzPopMax(string|array $key, string|int $timeout_or_key, mixed ...$extra_args): array|false; - public function bzPopMin(string|array $key, string|int $timeout_or_key, mixed ...$extra_args): array; + public function bzPopMin(string|array $key, string|int $timeout_or_key, mixed ...$extra_args): array|false; public function clearLastError(): bool; @@ -79,21 +79,21 @@ class Redis { public function debug(string $key): string; - /** @return int|Redis */ + /** @return false|int|Redis */ public function decr(string $key, int $by = 1); - /** @return int|Redis */ + /** @return false|int|Redis */ public function decrBy(string $key, int $value); /** - * @return int|Redis + * @return false|int|Redis */ public function del(array|string $key, string ...$other_keys); /** * @deprecated * @alias Redis::del - * @return int|Redis + * @return false|int|Redis */ public function delete(array|string $key, string ...$other_keys); @@ -101,7 +101,7 @@ class Redis { public function dump(string $key): string; - /** @return string|Redis */ + /** @return false|string|Redis */ public function echo(string $str); public function eval(string $script, array $keys = null, int $num_keys = 0): mixed; @@ -125,7 +125,7 @@ class Redis { public function geodist(string $key, string $src, string $dst, ?string $unit = null): Redis|float|false; - public function geohash(string $key, string $member, string ...$other_members): array; + public function geohash(string $key, string $member, string ...$other_members): array|false; public function geopos(string $key, string $member, string ...$other_members): Redis|array|false; @@ -137,16 +137,16 @@ class Redis { public function georadiusbymember_ro(string $key, string $member, float $radius, string $unit, array $options = []): Redis|mixed|false; - public function geosearch(string $key, array|string $position, array|int|float $shape, string $unit, array $options = []): array; + public function geosearch(string $key, array|string $position, array|int|float $shape, string $unit, array $options = []): array|false; - public function geosearchstore(string $dst, string $src, array|string $position, array|int|float $shape, string $unit, array $options = []): array; + public function geosearchstore(string $dst, string $src, array|string $position, array|int|float $shape, string $unit, array $options = []): array|false; /** @return false|string|Redis */ public function get(string $key); public function getAuth(): mixed; - /** @return int|Redis */ + /** @return false|int|Redis */ public function getBit(string $key, int $idx); public function getDBNum(): int; @@ -163,12 +163,12 @@ class Redis { public function getPort(): int; - /** @return string|Redis */ + /** @return false|string|Redis */ public function getRange(string $key, int $start, int $end); public function getReadTimeout(): int; - /** @return string|Redis */ + /** @return false|string|Redis */ public function getset(string $key, mixed $value); public function getTimeout(): int; @@ -203,25 +203,25 @@ class Redis { public function hscan(string $key, ?int &$iterator, ?string $pattern = null, int $count = 0): bool|array; - /** @return int|Redis */ + /** @return false|int|Redis */ public function incr(string $key, int $by = 1); - /** @return int|Redis */ + /** @return false|int|Redis */ public function incrBy(string $key, int $value); - /** @return int|Redis */ + /** @return false|int|Redis */ public function incrByFloat(string $key, float $value); public function info(string $opt = null): Redis|array|false; public function isConnected(): bool; - /** @return array|Redis */ + /** @return false|array|Redis */ public function keys(string $pattern); /** * @param mixed $elements - * @return int|Redis + * @return false|int|Redis */ public function lInsert(string $key, string $pos, mixed $pivot, mixed $value); @@ -230,25 +230,25 @@ class Redis { public function lMove(string $src, string $dst, string $wherefrom, string $whereto): string; - /** @return string|Redis */ + /** @return false|string|Redis */ public function lPop(string $key); /** * @param mixed $elements - * @return int|Redis + * @return false|int|Redis */ public function lPush(string $key, ...$elements); /** * @param mixed $elements - * @return int|Redis + * @return false|int|Redis */ public function rPush(string $key, ...$elements); - /** @return int|Redis */ + /** @return false|int|Redis */ public function lPushx(string $key, mixed $value); - /** @return int|Redis */ + /** @return false|int|Redis */ public function rPushx(string $key, mixed $value); public function lSet(string $key, int $index, mixed $value): Redis|bool; @@ -266,7 +266,7 @@ class Redis { public function ltrim(string $key, int $start , int $end): Redis|bool; - /** @return array|Redis */ + /** @return false|array|Redis */ public function mget(array $keys); public function migrate(string $host, int $port, string $key, string $dst, int $timeout, bool $copy = false, bool $replace = false): bool; @@ -301,7 +301,7 @@ public function persist(string $key): bool; public function pfmerge(string $dst, array $keys): bool; - /** @return string|Redis */ + /** @return false|string|Redis */ public function ping(string $key = NULL); public function pipeline(): bool|Redis; @@ -323,12 +323,12 @@ public function persist(string $key): bool; public function pubsub(string $command, mixed $arg = null): mixed; - public function punsubscribe(array $patterns): array; + public function punsubscribe(array $patterns): array|false; - /** @return string|Redis */ + /** @return false|string|Redis */ public function rPop(string $key); - /** @return string|Redis */ + /** @return false|string|Redis */ public function randomKey(); public function rawcommand(string $command, mixed ...$args): mixed; @@ -359,7 +359,7 @@ public function persist(string $key): bool; public function sMembers(string $key): Redis|array|false; - public function sMisMember(string $key, string $member, string ...$other_members): array; + public function sMisMember(string $key, string $member, string ...$other_members): array|false; public function sMove(string $src, string $dst, mixed $value): Redis|bool; @@ -384,10 +384,10 @@ public function persist(string $key): bool; /** @return bool|Redis */ public function set(string $key, mixed $value, mixed $opt = NULL); - /** @return int|Redis */ + /** @return false|int|Redis */ public function setBit(string $key, int $idx, bool $value); - /** @return int|Redis */ + /** @return false|int|Redis */ public function setRange(string $key, int $start, string $value); @@ -431,26 +431,26 @@ public function persist(string $key): bool; public function sscan(string $key, int &$iterator, ?string $pattern = null, int $count = 0): array|false; - /** @return int|Redis */ + /** @return false|int|Redis */ public function strlen(string $key); - public function subscribe(string $channel, string ...$other_channels): array; + public function subscribe(string $channel, string ...$other_channels): array|false; public function swapdb(string $src, string $dst): bool; - public function time(): array; + public function time(): array|false; public function ttl(string $key): Redis|int|false; - /** @return int|Redis */ + /** @return false|int|Redis */ public function type(string $key); /** - * @return int|Redis + * @return false|int|Redis */ public function unlink(array|string $key, string ...$other_keys); - public function unsubscribe(string $channel, string ...$other_channels): array; + public function unsubscribe(string $channel, string ...$other_channels): array|false; /** @return bool|Redis */ public function unwatch(); @@ -466,7 +466,7 @@ public function persist(string $key): bool; public function xadd(string $key, string $id, array $values, int $maxlen = 0, bool $approx = false): string|false; - public function xclaim(string $key, string $group, string $consumer, int $min_iddle, array $ids, array $options): string|array; + public function xclaim(string $key, string $group, string $consumer, int $min_iddle, array $ids, array $options): string|array|false; public function xdel(string $key, array $ids): Redis|int|false; @@ -498,25 +498,25 @@ public function persist(string $key): bool; public function zLexCount(string $key, string $min, string $max): Redis|int|false; - public function zMscore(string $key, string $member, string ...$other_members): array; + public function zMscore(string $key, string $member, string ...$other_members): array|false; - public function zPopMax(string $key, int $value = null): array; + public function zPopMax(string $key, int $value = null): array|false; - public function zPopMin(string $key, int $value = null): array; + public function zPopMin(string $key, int $value = null): array|false; public function zRange(string $key, int $start, int $end, mixed $scores = null): Redis|array|false; - public function zRangeByLex(string $key, string $min, string $max, int $offset = -1, int $count = -1): array; + public function zRangeByLex(string $key, string $min, string $max, int $offset = -1, int $count = -1): array|false; public function zRangeByScore(string $key, string $start, string $end, array $options = []): Redis|array|false; - public function zRandMember(string $key, array $options = null): string|array; + public function zRandMember(string $key, array $options = null): string|array|false; public function zRank(string $key, mixed $member): Redis|int|false; public function zRem(mixed $key, mixed $member, mixed ...$other_members): Redis|int|false; - public function zRemRangeByLex(string $key, string $min, string $max): int; + public function zRemRangeByLex(string $key, string $min, string $max): int|false; public function zRemRangeByRank(string $key, int $start, int $end): Redis|int|false; @@ -524,15 +524,15 @@ public function persist(string $key): bool; public function zRevRange(string $key, int $start, int $end, mixed $scores = null): Redis|array|false; - public function zRevRangeByLex(string $key, string $min, string $max, int $offset = -1, int $count = -1): array; + public function zRevRangeByLex(string $key, string $min, string $max, int $offset = -1, int $count = -1): array|false; - public function zRevRangeByScore(string $key, string $start, string $end, array $options = []): array; + public function zRevRangeByScore(string $key, string $start, string $end, array $options = []): array|false; public function zRevRank(string $key, mixed $member): Redis|int|false; public function zScore(string $key, mixed $member): Redis|float|false; - public function zdiff(array $keys, array $options = null): array; + public function zdiff(array $keys, array $options = null): array|false; public function zdiffstore(string $dst, array $keys, array $options = null): int; From 47317205c19039cc8c8bb653e1646c7bffacc717 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Sun, 9 Oct 2022 16:51:26 +0200 Subject: [PATCH 152/178] small improvement for return type of mGet --- stubs/phpredis.phpstub | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stubs/phpredis.phpstub b/stubs/phpredis.phpstub index 8831306420f..93c65ab4611 100644 --- a/stubs/phpredis.phpstub +++ b/stubs/phpredis.phpstub @@ -266,7 +266,7 @@ class Redis { public function ltrim(string $key, int $start , int $end): Redis|bool; - /** @return false|array|Redis */ + /** @return false|list|Redis */ public function mget(array $keys); public function migrate(string $host, int $port, string $key, string $dst, int $timeout, bool $copy = false, bool $replace = false): bool; From 5bfc0f960be71093c4b4dc754aa94c6142b44bd9 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Sun, 9 Oct 2022 16:53:30 +0200 Subject: [PATCH 153/178] force $value to be string technically all stringable types work https://github.com/phpredis/phpredis/issues/1735#event-7529843256 however they're all cast to string implicitly, which unevitably leads to unexpected results (see riskyCast,...) --- stubs/phpredis.phpstub | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/stubs/phpredis.phpstub b/stubs/phpredis.phpstub index 93c65ab4611..147287dddfb 100644 --- a/stubs/phpredis.phpstub +++ b/stubs/phpredis.phpstub @@ -273,9 +273,15 @@ class Redis { public function move(string $key, int $index): bool; - public function mset(array $key_values): Redis|bool; + /** + * @param array + */ + public function mset($key_values): Redis|bool; - public function msetnx(array $key_values): Redis|bool; + /** + * @param array + */ + public function msetnx($key_values): Redis|bool; public function multi(int $value = Redis::MULTI): bool|Redis; @@ -382,7 +388,7 @@ public function persist(string $key): bool; public function select(int $db): bool; /** @return bool|Redis */ - public function set(string $key, mixed $value, mixed $opt = NULL); + public function set(string $key, string $value, mixed $opt = NULL); /** @return false|int|Redis */ public function setBit(string $key, int $idx, bool $value); @@ -394,10 +400,10 @@ public function persist(string $key): bool; public function setOption(int $option, mixed $value): bool; /** @return bool|Redis */ - public function setex(string $key, int $expire, mixed $value); + public function setex(string $key, int $expire, string $value); /** @return bool|array|Redis */ - public function setnx(string $key, mixed $value); + public function setnx(string $key, string $value); public function sismember(string $key, mixed $value): Redis|bool; From 88ba8452c4dcdfb83e8f63c4bd68e54d8e2f5846 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Sun, 9 Oct 2022 17:01:38 +0200 Subject: [PATCH 154/178] some more string values --- stubs/phpredis.phpstub | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/stubs/phpredis.phpstub b/stubs/phpredis.phpstub index 147287dddfb..0ad1780d22d 100644 --- a/stubs/phpredis.phpstub +++ b/stubs/phpredis.phpstub @@ -32,7 +32,7 @@ class Redis { public function acl(string $subcmd, ...$args); /** @return false|int|Redis */ - public function append(string $key, mixed $value); + public function append(string $key, string $value); public function auth(mixed $credentials): bool; @@ -169,7 +169,7 @@ class Redis { public function getReadTimeout(): int; /** @return false|string|Redis */ - public function getset(string $key, mixed $value); + public function getset(string $key, string $value); public function getTimeout(): int; @@ -193,7 +193,7 @@ class Redis { public function hMset(string $key, array $keyvals): Redis|bool|false; - public function hSet(string $key, string $member, mixed $value): Redis|int|false; + public function hSet(string $key, string $member, string $value): Redis|int|false; public function hSetNx(string $key, string $member, string $value): Redis|bool; @@ -246,12 +246,12 @@ class Redis { public function rPush(string $key, ...$elements); /** @return false|int|Redis */ - public function lPushx(string $key, mixed $value); + public function lPushx(string $key, string $value); /** @return false|int|Redis */ - public function rPushx(string $key, mixed $value); + public function rPushx(string $key, string $value); - public function lSet(string $key, int $index, mixed $value): Redis|bool; + public function lSet(string $key, int $index, string $value): Redis|bool; public function lastSave(): int; @@ -262,7 +262,7 @@ class Redis { /** * @return int|Redis|false */ - public function lrem(string $key, mixed $value, int $count = 0); + public function lrem(string $key, string $value, int $count = 0); public function ltrim(string $key, int $start , int $end): Redis|bool; @@ -319,7 +319,7 @@ public function persist(string $key): bool; public function popen(string $host, int $port = 6379, float $timeout = 0, string $persistent_id = NULL, int $retry_interval = 0, float $read_timeout = 0, array $context = NULL): bool; /** @return bool|Redis */ - public function psetex(string $key, int $expire, mixed $value); + public function psetex(string $key, int $expire, string $value); public function psubscribe(array $patterns): void; @@ -351,7 +351,7 @@ public function persist(string $key): bool; public function rpoplpush(string $src, string $dst): Redis|string|false; - public function sAdd(string $key, mixed $value, mixed ...$other_values): Redis|int|false; + public function sAdd(string $key, string $value, mixed ...$other_values): Redis|int|false; public function sAddArray(string $key, array $values): int; @@ -367,7 +367,7 @@ public function persist(string $key): bool; public function sMisMember(string $key, string $member, string ...$other_members): array|false; - public function sMove(string $src, string $dst, mixed $value): Redis|bool; + public function sMove(string $src, string $dst, string $value): Redis|bool; public function sPop(string $key, int $count = 0): Redis|string|array|false; @@ -405,7 +405,7 @@ public function persist(string $key): bool; /** @return bool|array|Redis */ public function setnx(string $key, string $value); - public function sismember(string $key, mixed $value): Redis|bool; + public function sismember(string $key, string $value): Redis|bool; public function slaveof(string $host = null, int $port = 6379): bool; @@ -433,7 +433,7 @@ public function persist(string $key): bool; */ public function sortDescAlpha(string $key, ?string $pattern = null, mixed $get = null, int $offset = -1, int $count = -1, ?string $store = null): array; - public function srem(string $key, mixed $value, mixed ...$other_values): Redis|int|false; + public function srem(string $key, string $value, mixed ...$other_values): Redis|int|false; public function sscan(string $key, int &$iterator, ?string $pattern = null, int $count = 0): array|false; From b5f6da72850cf87a9432143e12db38c094fabd2b Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Mon, 10 Oct 2022 14:31:35 +0200 Subject: [PATCH 155/178] add common phpunit $_SERVER values bool Fix https://github.com/vimeo/psalm/issues/8556 --- .../Statements/Expression/Fetch/VariableFetchAnalyzer.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php index 303caa17ce1..4a10589196a 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php @@ -645,6 +645,9 @@ public static function getGlobalType(string $var_id, int $codebase_analysis_php_ $request_time_float_helper = Type::getFloat(); $request_time_float_helper->possibly_undefined = true; + $bool_helper = Type::getBool(); + $bool_helper->possibly_undefined = true; + $detailed_type = new TKeyedArray([ // https://www.php.net/manual/en/reserved.variables.server.php 'PHP_SELF' => $non_empty_string_helper, @@ -719,6 +722,9 @@ public static function getGlobalType(string $var_id, int $codebase_analysis_php_ 'HTTP_SEC_CH_UA_PLATFORM' => $non_empty_string_helper, 'HTTP_SEC_CH_UA_MOBILE' => $non_empty_string_helper, 'HTTP_SEC_CH_UA' => $non_empty_string_helper, + // phpunit + 'APP_DEBUG' => $bool_helper, + 'APP_ENV' => $string_helper, ]); // generic case for all other elements From fa5305048368a6ab12dd384c2ce7af675c35e7b6 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Mon, 10 Oct 2022 16:07:14 +0200 Subject: [PATCH 156/178] fix $_FILES --- .../Expression/Fetch/VariableFetchAnalyzer.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php index 4a10589196a..3fc1b8c7b9f 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php @@ -13,6 +13,7 @@ use Psalm\Internal\Codebase\TaintFlowGraph; use Psalm\Internal\DataFlow\DataFlowNode; use Psalm\Internal\DataFlow\TaintSource; +use Psalm\Internal\Type\TypeCombiner; use Psalm\Issue\ImpureVariable; use Psalm\Issue\InvalidScope; use Psalm\Issue\PossiblyUndefinedGlobalVariable; @@ -767,7 +768,14 @@ public static function getGlobalType(string $var_id, int $codebase_analysis_php_ $type = new TKeyedArray($values); - return new Union([$type]); + // $_FILES['userfile']['...'] case + $named_type = new TArray([Type::getNonEmptyString(), new Union([$type])]); + + // by default $_FILES is an empty array + $default_type = new TArray([Type::getNever(), Type::getNever()]); + + // ideally we would have 3 separate arrays with distinct types, but that isn't possible with psalm atm + return TypeCombiner::combine([$default_type, $type, $named_type]); } if ($var_id === '$_SESSION') { From 1e6019dddeefb8fad7017df3503f1b0ad0e37a9e Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Mon, 10 Oct 2022 16:18:27 +0200 Subject: [PATCH 157/178] size and error in $_FILES more specific --- .../Statements/Expression/Fetch/VariableFetchAnalyzer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php index 3fc1b8c7b9f..ae2bac6ac40 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php @@ -746,7 +746,7 @@ public static function getGlobalType(string $var_id, int $codebase_analysis_php_ new TNonEmptyList(Type::getString()), ]), 'size' => new Union([ - new TInt(), + new TIntRange(0, null), new TNonEmptyList(Type::getInt()), ]), 'tmp_name' => new Union([ @@ -754,7 +754,7 @@ public static function getGlobalType(string $var_id, int $codebase_analysis_php_ new TNonEmptyList(Type::getString()), ]), 'error' => new Union([ - new TInt(), + new TIntRange(0, 8), new TNonEmptyList(Type::getInt()), ]), ]; From 0da493b5bb93e7bffec31db3513f2b9c4234ba20 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Mon, 10 Oct 2022 16:28:46 +0200 Subject: [PATCH 158/178] fix docs to match example --- .../Statements/Expression/Fetch/VariableFetchAnalyzer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php index ae2bac6ac40..86823148326 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php @@ -774,7 +774,7 @@ public static function getGlobalType(string $var_id, int $codebase_analysis_php_ // by default $_FILES is an empty array $default_type = new TArray([Type::getNever(), Type::getNever()]); - // ideally we would have 3 separate arrays with distinct types, but that isn't possible with psalm atm + // ideally we would have 4 separate arrays with distinct types, but that isn't possible with psalm atm return TypeCombiner::combine([$default_type, $type, $named_type]); } From 3a420f4f7a1d59e4362123327883218d4d5ede1c Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Mon, 10 Oct 2022 17:06:00 +0200 Subject: [PATCH 159/178] phpunit bool|string --- .../Statements/Expression/Fetch/VariableFetchAnalyzer.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php index 86823148326..fb358a1a0be 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php @@ -646,8 +646,8 @@ public static function getGlobalType(string $var_id, int $codebase_analysis_php_ $request_time_float_helper = Type::getFloat(); $request_time_float_helper->possibly_undefined = true; - $bool_helper = Type::getBool(); - $bool_helper->possibly_undefined = true; + $bool_string_helper = new Union([new Bool(), new TString()]); + $bool_string_helper->possibly_undefined = true; $detailed_type = new TKeyedArray([ // https://www.php.net/manual/en/reserved.variables.server.php @@ -724,7 +724,7 @@ public static function getGlobalType(string $var_id, int $codebase_analysis_php_ 'HTTP_SEC_CH_UA_MOBILE' => $non_empty_string_helper, 'HTTP_SEC_CH_UA' => $non_empty_string_helper, // phpunit - 'APP_DEBUG' => $bool_helper, + 'APP_DEBUG' => $bool_string_helper, 'APP_ENV' => $string_helper, ]); From 0f6891c7572050080323c9b4fe552227bae9569c Mon Sep 17 00:00:00 2001 From: orklah Date: Mon, 10 Oct 2022 18:07:26 +0200 Subject: [PATCH 160/178] fix typo --- .../Statements/Expression/Fetch/VariableFetchAnalyzer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php index fb358a1a0be..2739b032ad9 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php @@ -646,7 +646,7 @@ public static function getGlobalType(string $var_id, int $codebase_analysis_php_ $request_time_float_helper = Type::getFloat(); $request_time_float_helper->possibly_undefined = true; - $bool_string_helper = new Union([new Bool(), new TString()]); + $bool_string_helper = new Union([new TBool(), new TString()]); $bool_string_helper->possibly_undefined = true; $detailed_type = new TKeyedArray([ From 60129819f575e0fa0188afc5b4ed0fe81b58205b Mon Sep 17 00:00:00 2001 From: orklah Date: Mon, 10 Oct 2022 18:10:49 +0200 Subject: [PATCH 161/178] add import --- .../Statements/Expression/Fetch/VariableFetchAnalyzer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php index 2739b032ad9..d268f021994 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php @@ -23,6 +23,7 @@ use Psalm\IssueBuffer; use Psalm\Type; use Psalm\Type\Atomic\TArray; +use Psalm\Type\Atomic\TBool; use Psalm\Type\Atomic\TInt; use Psalm\Type\Atomic\TIntRange; use Psalm\Type\Atomic\TKeyedArray; From 68f6ba873e7f6fdae9786af69db0c9f9b7649a43 Mon Sep 17 00:00:00 2001 From: Steven Dickinson Date: Tue, 11 Oct 2022 14:11:58 +0100 Subject: [PATCH 162/178] Fix MinMaxReturnTypeProvider when handling TDependentListKeys --- .../MinMaxReturnTypeProvider.php | 10 ++-- .../MinMaxReturnTypeProviderTest.php | 53 +++++++++++++++++++ 2 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 tests/ReturnTypeProvider/MinMaxReturnTypeProviderTest.php diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php index 1a24e22b30b..e23ff30b125 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php @@ -8,17 +8,16 @@ use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent; use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface; use Psalm\Type; +use Psalm\Type\Atomic\TDependentListKey; use Psalm\Type\Atomic\TInt; use Psalm\Type\Atomic\TIntRange; use Psalm\Type\Atomic\TLiteralInt; use Psalm\Type\Atomic\TPositiveInt; use Psalm\Type\Union; -use UnexpectedValueException; use function array_filter; use function assert; use function count; -use function get_class; use function in_array; use function max; use function min; @@ -72,11 +71,12 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev } elseif ($atomic_type instanceof TPositiveInt) { $min_bounds[] = 1; $max_bounds[] = null; - } elseif (get_class($atomic_type) === TInt::class) { + } elseif ($atomic_type instanceof TDependentListKey) { + $min_bounds[] = 0; + $max_bounds[] = null; + } else {//already guarded by the `instanceof TInt` check above $min_bounds[] = null; $max_bounds[] = null; - } else { - throw new UnexpectedValueException('Unexpected type'); } } } else { diff --git a/tests/ReturnTypeProvider/MinMaxReturnTypeProviderTest.php b/tests/ReturnTypeProvider/MinMaxReturnTypeProviderTest.php new file mode 100644 index 00000000000..1bbdff97d82 --- /dev/null +++ b/tests/ReturnTypeProvider/MinMaxReturnTypeProviderTest.php @@ -0,0 +1,53 @@ + [ + ' 'int', + '$max' => 'int', + ], + ]; + yield 'nonInt' => [ + ' 'string', + '$max' => 'string', + ], + ]; + yield 'maxIntRange' => [ + ' $v) { + if ($v === "") $h0 = $i; + if ($v === "") $h1 = $i; + } + if ($h0 === null || $h1 === null) throw new \Exception(); + + $min = min($h0, $h1); + $max = max($h0, $h1); + ', + [ + '$min' => 'int<0, max>', + '$max' => 'int<0, max>', + ], + ]; + } +} From f573ef5163e31b4104df0850a6e762893bb6ab70 Mon Sep 17 00:00:00 2001 From: Greg Hargreaves Date: Fri, 14 Oct 2022 00:49:57 +0100 Subject: [PATCH 163/178] Correct return type of DateTimeImmutable sub method stub --- stubs/CoreImmutableClasses.phpstub | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/stubs/CoreImmutableClasses.phpstub b/stubs/CoreImmutableClasses.phpstub index 23eb77bdb77..820275da5bf 100644 --- a/stubs/CoreImmutableClasses.phpstub +++ b/stubs/CoreImmutableClasses.phpstub @@ -58,8 +58,7 @@ class DateTimeImmutable implements DateTimeInterface /** * @psalm-mutation-free - * @return static|false this method can fail in case an {@see DateInterval} with relative - * week days is passed in. + * @return static * * @see https://github.com/php/php-src/blob/534127d3b22b193ffb9511c4447584f0d2bd4e24/ext/date/php_date.c#L3157-L3160 */ From ef0d2256a6caf9b7464a69f8a750592910227b6d Mon Sep 17 00:00:00 2001 From: Greg Hargreaves Date: Fri, 14 Oct 2022 00:56:20 +0100 Subject: [PATCH 164/178] Remove link to php-src as was part of the documented reason for the return type false --- stubs/CoreImmutableClasses.phpstub | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/stubs/CoreImmutableClasses.phpstub b/stubs/CoreImmutableClasses.phpstub index 820275da5bf..91c0a41c69c 100644 --- a/stubs/CoreImmutableClasses.phpstub +++ b/stubs/CoreImmutableClasses.phpstub @@ -59,8 +59,7 @@ class DateTimeImmutable implements DateTimeInterface /** * @psalm-mutation-free * @return static - * - * @see https://github.com/php/php-src/blob/534127d3b22b193ffb9511c4447584f0d2bd4e24/ext/date/php_date.c#L3157-L3160 + */ public function sub(DateInterval $interval) {} From 8849e8ca3878cb35f3047cdedea36cac321058b3 Mon Sep 17 00:00:00 2001 From: Greg Hargreaves Date: Fri, 14 Oct 2022 00:59:54 +0100 Subject: [PATCH 165/178] Fix test for DateTimeImmutable sub method return type --- tests/MethodCallTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/MethodCallTest.php b/tests/MethodCallTest.php index cb3103fdeb8..377a3737805 100644 --- a/tests/MethodCallTest.php +++ b/tests/MethodCallTest.php @@ -271,7 +271,7 @@ final class MyDate extends DateTimeImmutable {} $b = (new DateTimeImmutable())->modify("+3 hours");', 'assertions' => [ - '$yesterday' => 'MyDate|false', + '$yesterday' => 'MyDate', '$b' => 'DateTimeImmutable', ], ], From 06581ce4b0e5751446eec44075c48f7bd9e61dc6 Mon Sep 17 00:00:00 2001 From: Greg Hargreaves Date: Fri, 14 Oct 2022 01:54:06 +0100 Subject: [PATCH 166/178] Add additional checks for concat of non-empty strings to return non-falsy --- .../Expression/BinaryOp/ConcatAnalyzer.php | 25 +++++++++++++++--- src/Psalm/Type.php | 8 ++++++ tests/BinaryOperationTest.php | 26 +++++++++++++++++++ 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php index e317de46e75..ff0e8eef4a0 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php @@ -199,6 +199,17 @@ public static function analyze( $numeric_type ); + $numeric_type = Type::getNumericString(); + $numeric_type->addType(new TInt()); + $numeric_type->addType(new TFloat()); + $right_is_numeric = UnionTypeComparator::isContainedBy( + $codebase, + $right_type, + $numeric_type + ); + + $has_numeric_type = $left_is_numeric || $right_is_numeric; + if ($left_is_numeric) { $right_uint = Type::getPositiveInt(); $right_uint->addType(new TLiteralInt(0)); @@ -230,16 +241,23 @@ public static function analyze( $non_empty_string = clone $numeric_type; $non_empty_string->addType(new TNonEmptyString()); - $has_non_empty = UnionTypeComparator::isContainedBy( + $left_non_empty = UnionTypeComparator::isContainedBy( $codebase, $left_type, $non_empty_string - ) || UnionTypeComparator::isContainedBy( + ); + + $right_non_empty = UnionTypeComparator::isContainedBy( $codebase, $right_type, $non_empty_string ); + $has_non_empty = $left_non_empty || $right_non_empty; + $all_non_empty = $left_non_empty && $right_non_empty; + + $has_numeric_and_non_empty = $has_numeric_type && $has_non_empty; + $all_literals = $left_type->allLiterals() && $right_type->allLiterals(); if ($has_non_empty) { @@ -248,7 +266,8 @@ public static function analyze( } elseif ($all_lowercase) { $result_type = Type::getNonEmptyLowercaseString(); } else { - $result_type = Type::getNonEmptyString(); + $result_type = $all_non_empty || $has_numeric_and_non_empty ? + Type::getNonFalsyString() : Type::getNonEmptyString(); } } else { if ($all_literals) { diff --git a/src/Psalm/Type.php b/src/Psalm/Type.php index 35ca811372e..15d7f1f2c3d 100644 --- a/src/Psalm/Type.php +++ b/src/Psalm/Type.php @@ -34,6 +34,7 @@ use Psalm\Type\Atomic\TNonEmptyList; use Psalm\Type\Atomic\TNonEmptyLowercaseString; use Psalm\Type\Atomic\TNonEmptyString; +use Psalm\Type\Atomic\TNonFalsyString; use Psalm\Type\Atomic\TNull; use Psalm\Type\Atomic\TNumeric; use Psalm\Type\Atomic\TNumericString; @@ -219,6 +220,13 @@ public static function getNonEmptyString(): Union return new Union([$type]); } + public static function getNonFalsyString(): Union + { + $type = new TNonFalsyString(); + + return new Union([$type]); + } + public static function getNumeric(): Union { $type = new TNumeric; diff --git a/tests/BinaryOperationTest.php b/tests/BinaryOperationTest.php index 241e573e27d..a9dc74982b0 100644 --- a/tests/BinaryOperationTest.php +++ b/tests/BinaryOperationTest.php @@ -830,6 +830,32 @@ function example(object $foo): string return ($foo instanceof FooInterface ? $foo->toString() : null) ?? "Not a stringable foo"; }', ], + 'concatNonEmptyReturnNonFalsyString' => [ + ' [ + '$a===' => 'non-falsy-string', + ], + ], + 'concatNumericWithNonEmptyReturnNonFalsyString' => [ + ' [ + '$a===' => 'non-falsy-string', + '$b===' => 'non-falsy-string', + ], + ], ]; } From b89ff32b7a2494444b7529da892d48a58d7ecac4 Mon Sep 17 00:00:00 2001 From: Greg Hargreaves Date: Fri, 14 Oct 2022 02:00:25 +0100 Subject: [PATCH 167/178] Remove duplicated numeric type declaration --- .../Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php index ff0e8eef4a0..44d5e5d4f7b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php @@ -199,9 +199,6 @@ public static function analyze( $numeric_type ); - $numeric_type = Type::getNumericString(); - $numeric_type->addType(new TInt()); - $numeric_type->addType(new TFloat()); $right_is_numeric = UnionTypeComparator::isContainedBy( $codebase, $right_type, From 4912651a9adbaea57d2ed80629743037a55331da Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sun, 16 Oct 2022 13:48:45 +0200 Subject: [PATCH 168/178] Fix --- src/Psalm/Internal/Provider/ParserCacheProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Provider/ParserCacheProvider.php b/src/Psalm/Internal/Provider/ParserCacheProvider.php index f8e7de875d8..2d4583d7c9f 100644 --- a/src/Psalm/Internal/Provider/ParserCacheProvider.php +++ b/src/Psalm/Internal/Provider/ParserCacheProvider.php @@ -294,7 +294,7 @@ public function saveFileContentHashes(): void file_put_contents( $file_hashes_path, - json_encode($file_content_hashes, JSON_THROW_ON_ERROR) + json_encode($file_content_hashes, JSON_THROW_ON_ERROR), LOCK_EX ); } From 34000ca90d79a1e0f8e07ef160d7798d12b1e4c0 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sun, 16 Oct 2022 13:59:15 +0200 Subject: [PATCH 169/178] Fixes --- .../StaticMethod/ExistingAtomicStaticCallAnalyzer.php | 2 +- .../DateTimeModifyReturnTypeProvider.php | 3 +++ .../ReturnTypeProvider/MinMaxReturnTypeProvider.php | 1 + tests/AttributeTest.php | 6 +++--- tests/ClassTest.php | 2 +- tests/ClosureTest.php | 6 +++--- tests/DateTimeTest.php | 8 ++++---- tests/FunctionCallTest.php | 8 ++++---- tests/ReadonlyPropertyTest.php | 4 ++-- tests/ToStringTest.php | 6 +++--- tests/TypeReconciliation/ConditionalTest.php | 10 +++++----- tests/TypeReconciliation/ValueTest.php | 10 +--------- 12 files changed, 31 insertions(+), 35 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php index 4f03c0b4609..06ccbdaaab3 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php @@ -630,7 +630,7 @@ private static function getMethodReturnType( */ private static function hasStaticInType(Type\TypeNode $type): bool { - if ($type instanceof TNamedObject && ($type->value === 'static' || $type->was_static)) { + if ($type instanceof TNamedObject && ($type->value === 'static' || $type->is_static)) { return true; } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php index 1677ba64f6f..1b71f1a27db 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/DateTimeModifyReturnTypeProvider.php @@ -10,6 +10,9 @@ use Psalm\Type\Atomic\TLiteralString; use Psalm\Type\Union; +/** + * @internal + */ class DateTimeModifyReturnTypeProvider implements MethodReturnTypeProviderInterface { public static function getClassLikeNames(): array diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php index 50acfaba6b2..5942a356976 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php @@ -16,6 +16,7 @@ use Psalm\Type\Atomic\TList; use Psalm\Type\Atomic\TLiteralInt; use Psalm\Type\Union; +use UnexpectedValueException; use function array_filter; use function assert; diff --git a/tests/AttributeTest.php b/tests/AttributeTest.php index 180b81d1530..7318ecabaaf 100644 --- a/tests/AttributeTest.php +++ b/tests/AttributeTest.php @@ -256,7 +256,7 @@ public function getIterator() 'php_version' => '8.1' ], 'allowDynamicProperties' => [ - ' ' [ - ' ' '8.1', ], 'sensitiveParameterOnMethod' => [ - ' ' 'MissingTemplateParam', ], 'cannotNameClassConstantClass' => [ - ' ' */ diff --git a/tests/ClosureTest.php b/tests/ClosureTest.php index fb4da907449..53b3154cebc 100644 --- a/tests/ClosureTest.php +++ b/tests/ClosureTest.php @@ -566,7 +566,7 @@ function maker(string $className) { ], ], 'CallableWithArrayReduce' => [ - ' ' '8.1' ], 'FirstClassCallable:InheritedStaticMethod' => [ - ' ' [ - ' ' [ - ' ' [ - ' ' [ - ' ' [ - ' ' '8.1', ], 'trimSavesLowercaseAttribute' => [ - ' ' [ - ' ' [ - ' ' [ - ' ' [ diff --git a/tests/ReadonlyPropertyTest.php b/tests/ReadonlyPropertyTest.php index bec9accce71..b29548c518d 100644 --- a/tests/ReadonlyPropertyTest.php +++ b/tests/ReadonlyPropertyTest.php @@ -80,7 +80,7 @@ public function setBar(string $s) : void { echo (new A)->bar;' ], 'docblockReadonlyWithPrivateMutationsAllowedConstructorPropertySetInAnotherMethod' => [ - ' 'bar;' ], 'readonlyPublicConstructorPropertySetInAnotherMethod' => [ - ' ' 'ImplicitToStringCast' ], 'toStringTypecastNonString' => [ - ' ' 'InvalidCast', ], 'riskyArrayToIntCast' => [ - ' ' 'RiskyCast', ], 'riskyArrayToFloatCast' => [ - ' ' 'RiskyCast', ], diff --git a/tests/TypeReconciliation/ConditionalTest.php b/tests/TypeReconciliation/ConditionalTest.php index 8bc8e685b84..d5a320d9b90 100644 --- a/tests/TypeReconciliation/ConditionalTest.php +++ b/tests/TypeReconciliation/ConditionalTest.php @@ -2865,7 +2865,7 @@ function matches(string $value): bool { }' ], 'ctypeDigitMakesStringNumeric' => [ - ' ' [ - ' ' [ - ' ' [ - ' ' [ - ' ' [ - 'code' => ' [ - '$a===' => '5', - ], - ], 'falseDateInterval' => [ 'code' => ' [ - ' ' [ From 8518372cadb3cb1ee33633a4466284b321723386 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sun, 16 Oct 2022 14:23:31 +0200 Subject: [PATCH 170/178] Fixes --- .../Statements/Expression/AssertionFinder.php | 26 +++---------------- tests/MethodCallTest.php | 2 +- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php index 9fb04104814..09e411a90c4 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php @@ -124,28 +124,6 @@ class AssertionFinder public const ASSIGNMENT_TO_RIGHT = 1; public const ASSIGNMENT_TO_LEFT = -1; - public const IS_TYPE_CHECKS = [ - 'is_string' => ['string', [Type::class, 'getString']], - 'is_int' => ['int', [Type::class, 'getInt']], - 'is_integer' => ['int', [Type::class, 'getInt']], - 'is_long' => ['int', [Type::class, 'getInt']], - 'is_bool' => ['bool', [Type::class, 'getBool']], - 'is_resource' => ['resource', [Type::class, 'getResource']], - 'is_object' => ['object', [Type::class, 'getObject']], - 'array_is_list' => ['list', [Type::class, 'getList']], - 'is_array' => ['array', [Type::class, 'getArray']], - 'is_numeric' => ['numeric', [Type::class, 'getNumeric']], - 'is_null' => ['null', [Type::class, 'getNull']], - 'is_float' => ['float', [Type::class, 'getFloat']], - 'is_real' => ['float', [Type::class, 'getFloat']], - 'is_double' => ['float', [Type::class, 'getFloat']], - 'is_scalar' => ['scalar', [Type::class, 'getScalar']], - 'is_iterable' => ['iterable'], - 'is_countable' => ['countable'], - 'ctype_digit' => ['=numeric-string', [Type::class, 'getNumericString']], - 'ctype_lower' => ['non-empty-lowercase-string', [Type::class, 'getNonEmptyLowercaseString']], - ]; - /** * Gets all the type assertions in a conditional * @@ -1915,6 +1893,10 @@ private static function getIsAssertion(string $function_name): ?Assertion return new IsType(new Atomic\TIterable()); case 'is_countable': return new IsCountable(); + case 'ctype_digit': + return new IsType(new Atomic\TNumericString); + case 'ctype_lower': + return new IsType(new Atomic\TNonEmptyLowercaseString); } return null; diff --git a/tests/MethodCallTest.php b/tests/MethodCallTest.php index cf151c81615..8e2eaa3275d 100644 --- a/tests/MethodCallTest.php +++ b/tests/MethodCallTest.php @@ -973,7 +973,7 @@ public static function new() : self { class Datetime extends \DateTime { - public static function createFromInterface(\DatetimeInterface $datetime): static + public static function createFromInterface(\DateTimeInterface $datetime): static { return parent::createFromInterface($datetime); } From f816c06331a4846468fdaefde366c229cc3bfb21 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sun, 16 Oct 2022 14:39:34 +0200 Subject: [PATCH 171/178] Fix --- tests/JsonOutputTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/JsonOutputTest.php b/tests/JsonOutputTest.php index 780747b18ca..1ab0c0457d4 100644 --- a/tests/JsonOutputTest.php +++ b/tests/JsonOutputTest.php @@ -131,6 +131,7 @@ function fooFoo() { $a = $_GET["hello"]; assert(is_string($a)); if (is_string($a)) {}', + 'error_count' => 1, 'message' => 'Docblock-defined type string for $a is always string', 'line' => 4, 'error' => 'is_string($a)', From 53e3889745852409b704e0035d93e0819d522912 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 17 Oct 2022 10:21:26 +0200 Subject: [PATCH 172/178] Fixes --- tests/CoreStubsTest.php | 10 +-- .../ThrowsBlockAdditionTest.php | 72 +++++++++---------- tests/ReturnTypeProvider/ArrayColumnTest.php | 12 ++-- .../MinMaxReturnTypeProviderTest.php | 12 ++-- 4 files changed, 53 insertions(+), 53 deletions(-) diff --git a/tests/CoreStubsTest.php b/tests/CoreStubsTest.php index 358074130c9..5a60a348b5d 100644 --- a/tests/CoreStubsTest.php +++ b/tests/CoreStubsTest.php @@ -34,7 +34,7 @@ public function providerValidCodeParse(): iterable 'php_version' => '8.0', ]; yield 'Iterating over \DatePeriod (#5954) PHP7 Traversable' => [ - ' ' '7.3', ]; yield 'Iterating over \DatePeriod (#5954) PHP8 IteratorAggregate' => [ - ' ' '8.0', ]; yield 'Iterating over \DatePeriod (#5954), ISO string' => [ - ' ' '8.0', ]; yield 'DatePeriod implements only Traversable on PHP 7' => [ - ' ' '7.3', ]; yield 'DatePeriod implements IteratorAggregate on PHP 8' => [ - ' ' + * @return array */ public function providerValidCodeParse(): array { return [ 'addThrowsAnnotationToFunction' => [ - ' ' ' '7.4', + 'issues_to_fix' => ['MissingThrowsDocblock'], + 'safe_types' => true, ], 'addMultipleThrowsAnnotationToFunction' => [ - ' ' ' '7.4', + 'issues_to_fix' => ['MissingThrowsDocblock'], + 'safe_types' => true, ], 'preservesExistingThrowsAnnotationToFunction' => [ - ' ' ' '7.4', + 'issues_to_fix' => ['MissingThrowsDocblock'], + 'safe_types' => true, ], 'doesNotAddDuplicateThrows' => [ - ' ' ' '7.4', + 'issues_to_fix' => ['MissingThrowsDocblock'], + 'safe_types' => true, ], 'addThrowsAnnotationToFunctionInNamespace' => [ - ' ' ' '7.4', + 'issues_to_fix' => ['MissingThrowsDocblock'], + 'safe_types' => true, ], 'addThrowsAnnotationToFunctionFromFunctionFromOtherNamespace' => [ - ' ' ' '7.4', + 'issues_to_fix' => ['MissingThrowsDocblock'], + 'safe_types' => true, ], 'addThrowsAnnotationAccountsForUseStatements' => [ - ' ' ' '7.4', + 'issues_to_fix' => ['MissingThrowsDocblock'], + 'safe_types' => true, ], ]; } diff --git a/tests/ReturnTypeProvider/ArrayColumnTest.php b/tests/ReturnTypeProvider/ArrayColumnTest.php index 7be2a1ba06b..ea6a8d604f1 100644 --- a/tests/ReturnTypeProvider/ArrayColumnTest.php +++ b/tests/ReturnTypeProvider/ArrayColumnTest.php @@ -65,7 +65,7 @@ function f(array $shape): array { ]; yield 'arrayColumnWithObjectsAndColumnNameNull' => [ - ' ' [ - ' ' [ - ' ' [ - ' ' $instances */ @@ -129,7 +129,7 @@ function foo(object $object): void {} ]; yield 'arrayColumnWithListOfArrays' => [ - ' ' $arrays */ @@ -144,7 +144,7 @@ function foo(array $array): void {} public function providerInvalidCodeParse(): iterable { yield 'arrayColumnWithArrayAndColumnNameNull' => [ - ' ' $arrays */ $arrays = []; foreach (array_column($arrays, null, "name") as $array) { diff --git a/tests/ReturnTypeProvider/MinMaxReturnTypeProviderTest.php b/tests/ReturnTypeProvider/MinMaxReturnTypeProviderTest.php index 1bbdff97d82..da67246e2f3 100644 --- a/tests/ReturnTypeProvider/MinMaxReturnTypeProviderTest.php +++ b/tests/ReturnTypeProvider/MinMaxReturnTypeProviderTest.php @@ -12,27 +12,27 @@ class MinMaxReturnTypeProviderTest extends TestCase public function providerValidCodeParse(): iterable { yield 'literalInt' => [ - ' ' [ '$min' => 'int', '$max' => 'int', ], ]; yield 'nonInt' => [ - ' ' [ '$min' => 'string', '$max' => 'string', ], ]; yield 'maxIntRange' => [ - ' ' $v) { @@ -44,7 +44,7 @@ public function providerValidCodeParse(): iterable $min = min($h0, $h1); $max = max($h0, $h1); ', - [ + 'assertions' => [ '$min' => 'int<0, max>', '$max' => 'int<0, max>', ], From 8d4d0c0e4adfdae53b01730f9d2c3396ed29a8e1 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 17 Oct 2022 10:33:33 +0200 Subject: [PATCH 173/178] Fixes --- tests/FileManipulation/ThrowsBlockAdditionTest.php | 12 ++++++------ tests/FunctionCallTest.php | 8 -------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/tests/FileManipulation/ThrowsBlockAdditionTest.php b/tests/FileManipulation/ThrowsBlockAdditionTest.php index 889195569ea..467fe05b14b 100644 --- a/tests/FileManipulation/ThrowsBlockAdditionTest.php +++ b/tests/FileManipulation/ThrowsBlockAdditionTest.php @@ -119,7 +119,7 @@ function foo(string $s): string { 'safe_types' => true, ], 'addThrowsAnnotationToFunctionInNamespace' => [ - 'input_type' => ' ' ' ' true, ], 'addThrowsAnnotationToFunctionFromFunctionFromOtherNamespace' => [ - 'input_type' => ' ' ' ' true, ], 'addThrowsAnnotationAccountsForUseStatements' => [ - 'input_type' => ' ' ' ' 'lowercase-string', ], ], - 'round_literalValue' => [ - 'code' => ' [ - '$a===' => 'float(10.36)', - ], - ], ]; } From 150be5ca225504d38b1440c218caf0efb5df6275 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 17 Oct 2022 10:45:18 +0200 Subject: [PATCH 174/178] Update --- src/Psalm/Internal/Provider/ParserCacheProvider.php | 1 + tests/DateTimeTest.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Provider/ParserCacheProvider.php b/src/Psalm/Internal/Provider/ParserCacheProvider.php index 2d4583d7c9f..e1345240cee 100644 --- a/src/Psalm/Internal/Provider/ParserCacheProvider.php +++ b/src/Psalm/Internal/Provider/ParserCacheProvider.php @@ -211,6 +211,7 @@ private function getExistingFileContentHashes(): array throw new UnexpectedValueException('File content hashes should be in cache'); } + /** @psalm-suppress MixedAssignment */ $hashes_decoded = json_decode($hashes_encoded, true); if (!is_array($hashes_decoded)) { diff --git a/tests/DateTimeTest.php b/tests/DateTimeTest.php index 2dc62c3b08a..d5ae0d50d36 100644 --- a/tests/DateTimeTest.php +++ b/tests/DateTimeTest.php @@ -9,7 +9,7 @@ class DateTimeTest extends TestCase use ValidCodeAnalysisTestTrait; /** - * @return iterable,error_levels?:string[]}> + * @return array, error_levels?: list}> */ public function providerValidCodeParse(): iterable { From 1abade3c3049ac02e5881f8f4d81bdfd7ad40202 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 17 Oct 2022 12:14:07 +0200 Subject: [PATCH 175/178] Skip --- .../Statements/Expression/Fetch/VariableFetchAnalyzer.php | 2 +- src/Psalm/Internal/Provider/ParserCacheProvider.php | 5 +++-- src/Psalm/Internal/Provider/Providers.php | 2 +- .../ReturnTypeProvider/MinMaxReturnTypeProvider.php | 1 + src/Psalm/Internal/Provider/StatementsProvider.php | 2 +- tests/CastTest.php | 6 +++--- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php index 1872097914e..740daa859d7 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php @@ -778,7 +778,7 @@ public static function getGlobalType(string $var_id, int $codebase_analysis_php_ ]), ]; - if ($codebase_analysis_php_version_id >= 81000) { + if ($codebase_analysis_php_version_id >= 8_10_00) { $values['full_path'] = new Union([ new TString(), new TNonEmptyList(Type::getString()), diff --git a/src/Psalm/Internal/Provider/ParserCacheProvider.php b/src/Psalm/Internal/Provider/ParserCacheProvider.php index e1345240cee..559a11ecf63 100644 --- a/src/Psalm/Internal/Provider/ParserCacheProvider.php +++ b/src/Psalm/Internal/Provider/ParserCacheProvider.php @@ -11,6 +11,8 @@ use UnexpectedValueException; use function clearstatcache; +use function error_log; +use function file_get_contents; use function file_put_contents; use function filemtime; use function gettype; @@ -31,7 +33,6 @@ use function unserialize; use const DIRECTORY_SEPARATOR; -use const E_USER_ERROR; use const JSON_THROW_ON_ERROR; use const LOCK_EX; use const PHP_VERSION_ID; @@ -348,7 +349,7 @@ public function deleteOldParserCaches(float $time_before): int private function getParserCacheKey(string $file_path): string { - if (PHP_VERSION_ID >= 80100) { + if (PHP_VERSION_ID >= 8_01_00) { $hash = hash('xxh128', $file_path); } else { $hash = hash('md4', $file_path); diff --git a/src/Psalm/Internal/Provider/Providers.php b/src/Psalm/Internal/Provider/Providers.php index f8392f88242..864dd92a17f 100644 --- a/src/Psalm/Internal/Provider/Providers.php +++ b/src/Psalm/Internal/Provider/Providers.php @@ -90,7 +90,7 @@ public static function safeFileGetContents(string $path): string break; } $max_wait_cycles--; - usleep(50000); + usleep(50_000); } if (!$has_lock) { diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php index 5942a356976..3f9643448ea 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php @@ -21,6 +21,7 @@ use function array_filter; use function assert; use function count; +use function get_class; use function in_array; use function max; use function min; diff --git a/src/Psalm/Internal/Provider/StatementsProvider.php b/src/Psalm/Internal/Provider/StatementsProvider.php index e1c19fe232a..6094a952726 100644 --- a/src/Psalm/Internal/Provider/StatementsProvider.php +++ b/src/Psalm/Internal/Provider/StatementsProvider.php @@ -139,7 +139,7 @@ public function getStatementsForFile( $config = Config::getInstance(); - if (PHP_VERSION_ID >= 80100) { + if (PHP_VERSION_ID >= 8_01_00) { $file_content_hash = hash('xxh128', $version . $file_contents); } else { $file_content_hash = hash('md4', $version . $file_contents); diff --git a/tests/CastTest.php b/tests/CastTest.php index e14c6680993..2d20c4d3f02 100644 --- a/tests/CastTest.php +++ b/tests/CastTest.php @@ -13,7 +13,7 @@ class CastTest extends TestCase */ public function providerValidCodeParse(): iterable { - yield 'castFalseOrIntToInt' => [ + yield 'SKIPPED-castFalseOrIntToInt' => [ 'code' => ' */ $intOrFalse = 10; @@ -23,7 +23,7 @@ public function providerValidCodeParse(): iterable '$int===' => '0|int<10, 20>', ], ]; - yield 'castTrueOrIntToInt' => [ + yield 'SKIPPED-castTrueOrIntToInt' => [ 'code' => ' */ $intOrTrue = 10; @@ -33,7 +33,7 @@ public function providerValidCodeParse(): iterable '$int===' => '1|int<10, 20>', ], ]; - yield 'castBoolOrIntToInt' => [ + yield 'SKIPPED-castBoolOrIntToInt' => [ 'code' => ' */ $intOrBool = 10; From 15f5c593a74c229897e707f206309be9e7417245 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 17 Oct 2022 12:40:50 +0200 Subject: [PATCH 176/178] Fix --- dictionaries/CallMap.php | 4 +++- dictionaries/CallMap_80_delta.php | 4 ++++ dictionaries/CallMap_historical.php | 20 ++++++++++++++++++++ tests/FunctionCallTest.php | 14 +++++++------- tests/TaintTest.php | 7 ------- 5 files changed, 34 insertions(+), 15 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 4e3d07d364e..ea6fdd3e353 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1793,11 +1793,13 @@ 'DateTime::setTimestamp' => ['static', 'unixtimestamp'=>'int'], 'DateTime::setTimezone' => ['static', 'timezone'=>'DateTimeZone'], 'DateTime::sub' => ['static', 'interval'=>'DateInterval'], +'DateTimeImmutable::__construct' => ['void', 'time='=>'string'], +'DateTimeImmutable::__construct\'1' => ['void', 'time'=>'?string', 'timezone'=>'?DateTimeZone'], 'DateTimeImmutable::__set_state' => ['static', 'array'=>'array'], 'DateTimeImmutable::__wakeup' => ['void'], 'DateTimeImmutable::add' => ['static', 'interval'=>'DateInterval'], 'DateTimeImmutable::createFromFormat' => ['static|false', 'format'=>'string', 'time'=>'string', 'timezone='=>'?DateTimeZone'], -'DateTimeImmutable::createFromInterface' => ['self', 'object' => 'DateTimeInterface'], +'DateTimeImmutable::createFromInterface' => ['static', 'object' => 'DateTimeInterface'], 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], 'DateTimeImmutable::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeImmutable::format' => ['string', 'format'=>'string'], diff --git a/dictionaries/CallMap_80_delta.php b/dictionaries/CallMap_80_delta.php index b59c73bc3f3..6f46d086a23 100644 --- a/dictionaries/CallMap_80_delta.php +++ b/dictionaries/CallMap_80_delta.php @@ -49,6 +49,10 @@ 'old' => ['int|false'], 'new' => ['int'], ], + 'DateTimeImmutable::format' => [ + 'old' => ['string|false', 'format'=>'string'], + 'new' => ['string', 'format'=>'string'], + ], 'DateTimeImmutable::getTimestamp' => [ 'old' => ['int|false'], 'new' => ['int'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 97d739fa4d9..ac1c22c06e4 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1055,6 +1055,26 @@ 'DateTime::setTimestamp' => ['static', 'unixtimestamp'=>'int'], 'DateTime::setTimezone' => ['static', 'timezone'=>'DateTimeZone'], 'DateTime::sub' => ['static', 'interval'=>'DateInterval'], + 'DateTimeImmutable::__construct' => ['void', 'time='=>'string'], + 'DateTimeImmutable::__construct\'1' => ['void', 'time'=>'?string', 'timezone'=>'?DateTimeZone'], + 'DateTimeImmutable::__set_state' => ['static', 'array'=>'array'], + 'DateTimeImmutable::__wakeup' => ['void'], + 'DateTimeImmutable::add' => ['static', 'interval'=>'DateInterval'], + 'DateTimeImmutable::createFromFormat' => ['static|false', 'format'=>'string', 'time'=>'string', 'timezone='=>'?DateTimeZone'], + 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], + 'DateTimeImmutable::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], + 'DateTimeImmutable::format' => ['string|false', 'format'=>'string'], + 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], + 'DateTimeImmutable::getOffset' => ['int'], + 'DateTimeImmutable::getTimestamp' => ['int|false'], + 'DateTimeImmutable::getTimezone' => ['DateTimeZone|false'], + 'DateTimeImmutable::modify' => ['static', 'modify'=>'string'], + 'DateTimeImmutable::setDate' => ['static|false', 'year'=>'int', 'month'=>'int', 'day'=>'int'], + 'DateTimeImmutable::setISODate' => ['static|false', 'year'=>'int', 'week'=>'int', 'day='=>'int'], + 'DateTimeImmutable::setTime' => ['static|false', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], + 'DateTimeImmutable::setTimestamp' => ['static|false', 'unixtimestamp'=>'int'], + 'DateTimeImmutable::setTimezone' => ['static|false', 'timezone'=>'DateTimeZone'], + 'DateTimeImmutable::sub' => ['static|false', 'interval'=>'DateInterval'], 'DateTimeImmutable::__set_state' => ['static', 'array'=>'array'], 'DateTimeImmutable::__wakeup' => ['void'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}|false'], diff --git a/tests/FunctionCallTest.php b/tests/FunctionCallTest.php index b266b4db565..fbc5f690b8a 100644 --- a/tests/FunctionCallTest.php +++ b/tests/FunctionCallTest.php @@ -1493,14 +1493,14 @@ function test() : void { $y2 = date("Y", 10000); $F2 = date("F", 10000); /** @psalm-suppress MixedArgument */ - $F3 = date("F", $GLOBALS["F3"]);', + $F3 = date("F", $_GET["F3"]);', 'assertions' => [ - '$y' => 'numeric-string', - '$m' => 'numeric-string', - '$F' => 'string', - '$y2' => 'numeric-string', - '$F2' => 'string', - '$F3' => 'false|string', + '$y===' => 'numeric-string', + '$m===' => 'numeric-string', + '$F===' => 'string', + '$y2===' => 'numeric-string', + '$F2===' => 'string', + '$F3===' => 'false|string', ] ], 'sscanfReturnTypeWithTwoParameters' => [ diff --git a/tests/TaintTest.php b/tests/TaintTest.php index 0d68f9ef9fb..3058697a180 100644 --- a/tests/TaintTest.php +++ b/tests/TaintTest.php @@ -460,13 +460,6 @@ public static function slugify(string $url) : string { echo $a[0]["b"];', ], - 'intUntainted' => [ - 'code' => ' [ 'code' => ' Date: Mon, 17 Oct 2022 12:46:12 +0200 Subject: [PATCH 177/178] Re-apply changes manually --- dictionaries/CallMap.php | 17 ----------------- dictionaries/CallMap_80_delta.php | 8 -------- dictionaries/CallMap_historical.php | 20 -------------------- 3 files changed, 45 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index ea6fdd3e353..2c7587ff2b4 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -1793,27 +1793,10 @@ 'DateTime::setTimestamp' => ['static', 'unixtimestamp'=>'int'], 'DateTime::setTimezone' => ['static', 'timezone'=>'DateTimeZone'], 'DateTime::sub' => ['static', 'interval'=>'DateInterval'], -'DateTimeImmutable::__construct' => ['void', 'time='=>'string'], -'DateTimeImmutable::__construct\'1' => ['void', 'time'=>'?string', 'timezone'=>'?DateTimeZone'], 'DateTimeImmutable::__set_state' => ['static', 'array'=>'array'], 'DateTimeImmutable::__wakeup' => ['void'], -'DateTimeImmutable::add' => ['static', 'interval'=>'DateInterval'], -'DateTimeImmutable::createFromFormat' => ['static|false', 'format'=>'string', 'time'=>'string', 'timezone='=>'?DateTimeZone'], 'DateTimeImmutable::createFromInterface' => ['static', 'object' => 'DateTimeInterface'], -'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], -'DateTimeImmutable::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], -'DateTimeImmutable::format' => ['string', 'format'=>'string'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}|false'], -'DateTimeImmutable::getOffset' => ['int'], -'DateTimeImmutable::getTimestamp' => ['int'], -'DateTimeImmutable::getTimezone' => ['DateTimeZone|false'], -'DateTimeImmutable::modify' => ['static', 'modify'=>'string'], -'DateTimeImmutable::setDate' => ['static|false', 'year'=>'int', 'month'=>'int', 'day'=>'int'], -'DateTimeImmutable::setISODate' => ['static|false', 'year'=>'int', 'week'=>'int', 'day='=>'int'], -'DateTimeImmutable::setTime' => ['static|false', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], -'DateTimeImmutable::setTimestamp' => ['static|false', 'unixtimestamp'=>'int'], -'DateTimeImmutable::setTimezone' => ['static|false', 'timezone'=>'DateTimeZone'], -'DateTimeImmutable::sub' => ['static|false', 'interval'=>'DateInterval'], 'DateTimeInterface::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], 'DateTimeInterface::format' => ['string', 'format'=>'string'], 'DateTimeInterface::getOffset' => ['int'], diff --git a/dictionaries/CallMap_80_delta.php b/dictionaries/CallMap_80_delta.php index 6f46d086a23..815d07edb21 100644 --- a/dictionaries/CallMap_80_delta.php +++ b/dictionaries/CallMap_80_delta.php @@ -49,14 +49,6 @@ 'old' => ['int|false'], 'new' => ['int'], ], - 'DateTimeImmutable::format' => [ - 'old' => ['string|false', 'format'=>'string'], - 'new' => ['string', 'format'=>'string'], - ], - 'DateTimeImmutable::getTimestamp' => [ - 'old' => ['int|false'], - 'new' => ['int'], - ], 'DateTimeZone::listIdentifiers' => [ 'old' => ['list|false', 'timezoneGroup='=>'int', 'countryCode='=>'string|null'], 'new' => ['list', 'timezoneGroup='=>'int', 'countryCode='=>'string|null'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index ac1c22c06e4..97d739fa4d9 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -1055,26 +1055,6 @@ 'DateTime::setTimestamp' => ['static', 'unixtimestamp'=>'int'], 'DateTime::setTimezone' => ['static', 'timezone'=>'DateTimeZone'], 'DateTime::sub' => ['static', 'interval'=>'DateInterval'], - 'DateTimeImmutable::__construct' => ['void', 'time='=>'string'], - 'DateTimeImmutable::__construct\'1' => ['void', 'time'=>'?string', 'timezone'=>'?DateTimeZone'], - 'DateTimeImmutable::__set_state' => ['static', 'array'=>'array'], - 'DateTimeImmutable::__wakeup' => ['void'], - 'DateTimeImmutable::add' => ['static', 'interval'=>'DateInterval'], - 'DateTimeImmutable::createFromFormat' => ['static|false', 'format'=>'string', 'time'=>'string', 'timezone='=>'?DateTimeZone'], - 'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], - 'DateTimeImmutable::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], - 'DateTimeImmutable::format' => ['string|false', 'format'=>'string'], - 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], - 'DateTimeImmutable::getOffset' => ['int'], - 'DateTimeImmutable::getTimestamp' => ['int|false'], - 'DateTimeImmutable::getTimezone' => ['DateTimeZone|false'], - 'DateTimeImmutable::modify' => ['static', 'modify'=>'string'], - 'DateTimeImmutable::setDate' => ['static|false', 'year'=>'int', 'month'=>'int', 'day'=>'int'], - 'DateTimeImmutable::setISODate' => ['static|false', 'year'=>'int', 'week'=>'int', 'day='=>'int'], - 'DateTimeImmutable::setTime' => ['static|false', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], - 'DateTimeImmutable::setTimestamp' => ['static|false', 'unixtimestamp'=>'int'], - 'DateTimeImmutable::setTimezone' => ['static|false', 'timezone'=>'DateTimeZone'], - 'DateTimeImmutable::sub' => ['static|false', 'interval'=>'DateInterval'], 'DateTimeImmutable::__set_state' => ['static', 'array'=>'array'], 'DateTimeImmutable::__wakeup' => ['void'], 'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}|false'], From 56805ab089fbf394a988861f694b8f985d68ada1 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 17 Oct 2022 12:54:46 +0200 Subject: [PATCH 178/178] Fix --- tests/FunctionCallTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/FunctionCallTest.php b/tests/FunctionCallTest.php index fbc5f690b8a..9b4f21793c5 100644 --- a/tests/FunctionCallTest.php +++ b/tests/FunctionCallTest.php @@ -1493,7 +1493,7 @@ function test() : void { $y2 = date("Y", 10000); $F2 = date("F", 10000); /** @psalm-suppress MixedArgument */ - $F3 = date("F", $_GET["F3"]);', + $F3 = date("F", $GLOBALS["F3"]);', 'assertions' => [ '$y===' => 'numeric-string', '$m===' => 'numeric-string',