diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b0393de109..03420615cd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -32,6 +32,7 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" steps: - name: "Checkout" @@ -50,7 +51,7 @@ jobs: run: "composer install --no-interaction --no-progress" - name: "Transform source code" - if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' + if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" - name: "Lint" diff --git a/.github/workflows/reflection-golden-test.yml b/.github/workflows/reflection-golden-test.yml index 3c13f9205b..6d16f21aaa 100644 --- a/.github/workflows/reflection-golden-test.yml +++ b/.github/workflows/reflection-golden-test.yml @@ -71,6 +71,7 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" steps: - uses: Wandalen/wretry.action@v3.5.0 @@ -101,7 +102,7 @@ jobs: run: "composer install --no-interaction --no-progress" - name: "Transform source code" - if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' + if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' shell: bash run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" @@ -120,7 +121,7 @@ jobs: run: "composer install --no-interaction --no-progress" - name: "Transform source code" - if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' + if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' shell: bash run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index d38dd2726e..b19b6c10f7 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -38,6 +38,7 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" operating-system: [ubuntu-latest, windows-latest] steps: @@ -56,7 +57,7 @@ jobs: run: "composer install --no-interaction --no-progress" - name: "Transform source code" - if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' + if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' shell: bash run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" @@ -69,6 +70,10 @@ jobs: if: matrix.php-version == '7.2' run: "composer require --dev phpunit/phpunit:^8.5.31 brianium/paratest:^4.0 composer/semver:^1.2 --update-with-dependencies --ignore-platform-reqs" + - name: "Update PHPUnit" + if: matrix.php-version != '7.2' && matrix.php-version != '7.3' + run: "composer update phpunit/phpunit -W" + - name: "PHPStan" run: "make phpstan" @@ -85,6 +90,7 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" steps: - name: "Checkout" @@ -101,6 +107,9 @@ jobs: - name: "Install dependencies" run: "composer install --no-interaction --no-progress" + - name: "Update PHPUnit" + run: "composer update phpunit/phpunit -W" + - name: "Cache Result cache" uses: actions/cache@v4 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e86c7738fb..59d834e9c7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,6 +41,7 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" operating-system: [ ubuntu-latest, windows-latest ] steps: @@ -61,10 +62,14 @@ jobs: run: "composer install --no-interaction --no-progress" - name: "Transform source code" - if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' + if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' shell: bash run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" + - name: "Update PHPUnit" + if: matrix.php-version != '7.3' + run: "composer update phpunit/phpunit -W" + - name: "Tests" run: "make tests" diff --git a/build/composer-dependency-analyser.php b/build/composer-dependency-analyser.php index de637bfc79..723a3ece2e 100644 --- a/build/composer-dependency-analyser.php +++ b/build/composer-dependency-analyser.php @@ -19,6 +19,7 @@ 'symfony/process', 'symfony/service-contracts', 'symfony/string', + 'nette/php-generator', ]; return $config diff --git a/composer.json b/composer.json index a252b22197..e5f77ae753 100644 --- a/composer.json +++ b/composer.json @@ -18,17 +18,18 @@ "jetbrains/phpstorm-stubs": "dev-master#bdc8acd7c04c0c87197849c7cdd27e44b67b56c7", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", - "nette/neon": "^3.3.1", + "nette/neon": "3.3.3", + "nette/php-generator": "3.6.9", "nette/schema": "^1.2.2", "nette/utils": "^3.2.5", "nikic/php-parser": "^4.17.1", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.25.0.15", + "ondrejmirtes/better-reflection": "6.25.0.17", "phpstan/php-8-stubs": "0.3.97", "phpstan/phpdoc-parser": "1.29.1", "psr/http-message": "^1.1", "react/async": "^3", - "react/child-process": "^0.6.4", + "react/child-process": "^0.7", "react/dns": "^1.10", "react/event-loop": "^1.2", "react/http": "^1.1", @@ -81,14 +82,23 @@ "composer/ca-bundle": [ "patches/cloudflare-ca.patch" ], + "hoa/exception": [ + "patches/Idle.patch" + ], + "hoa/file": [ + "patches/File.patch", + "patches/Read.patch" + ], "hoa/iterator": [ "patches/Buffer.patch", "patches/Lookahead.patch" ], "hoa/compiler": [ "patches/HoaException.patch", + "patches/Invocation.patch", "patches/Rule.patch", - "patches/Lexer.patch" + "patches/Lexer.patch", + "patches/TreeNode.patch" ], "hoa/consistency": [ "patches/Consistency.patch" @@ -109,6 +119,10 @@ ], "nette/di": [ "patches/DependencyChecker.patch" + ], + "nette/neon": [ + "patches/NetteNeonStringNode.patch", + "patches/NeonParser.patch" ] } }, diff --git a/composer.lock b/composer.lock index 33d5165355..b50891bf2c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5f42c6fc299ea8b76be8124ba1f01218", + "content-hash": "758196456ce51136032914c3ba937aff", "packages": [ { "name": "clue/ndjson-react", @@ -1698,16 +1698,16 @@ }, { "name": "nette/neon", - "version": "v3.3.2", + "version": "v3.3.3", "source": { "type": "git", "url": "https://github.com/nette/neon.git", - "reference": "54b287d8c2cdbe577b02e28ca1713e275b05ece2" + "reference": "22e384da162fab42961d48eb06c06d3ad0c11b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/neon/zipball/54b287d8c2cdbe577b02e28ca1713e275b05ece2", - "reference": "54b287d8c2cdbe577b02e28ca1713e275b05ece2", + "url": "https://api.github.com/repos/nette/neon/zipball/22e384da162fab42961d48eb06c06d3ad0c11b95", + "reference": "22e384da162fab42961d48eb06c06d3ad0c11b95", "shasum": "" }, "require": { @@ -1760,27 +1760,27 @@ ], "support": { "issues": "https://github.com/nette/neon/issues", - "source": "https://github.com/nette/neon/tree/v3.3.2" + "source": "https://github.com/nette/neon/tree/v3.3.3" }, - "time": "2021-11-25T15:57:41+00:00" + "time": "2022-03-10T02:04:26+00:00" }, { "name": "nette/php-generator", - "version": "v3.6.5", + "version": "v3.6.9", "source": { "type": "git", "url": "https://github.com/nette/php-generator.git", - "reference": "9370403f9d9c25b51c4596ded1fbfe70347f7c82" + "reference": "d31782f7bd2ae84ad06f863391ec3fb77ca4d0a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/php-generator/zipball/9370403f9d9c25b51c4596ded1fbfe70347f7c82", - "reference": "9370403f9d9c25b51c4596ded1fbfe70347f7c82", + "url": "https://api.github.com/repos/nette/php-generator/zipball/d31782f7bd2ae84ad06f863391ec3fb77ca4d0a6", + "reference": "d31782f7bd2ae84ad06f863391ec3fb77ca4d0a6", "shasum": "" }, "require": { "nette/utils": "^3.1.2", - "php": ">=7.2 <8.2" + "php": ">=7.2 <8.3" }, "require-dev": { "nette/tester": "^2.4", @@ -1828,9 +1828,9 @@ ], "support": { "issues": "https://github.com/nette/php-generator/issues", - "source": "https://github.com/nette/php-generator/tree/v3.6.5" + "source": "https://github.com/nette/php-generator/tree/v3.6.9" }, - "time": "2021-11-24T16:23:44+00:00" + "time": "2022-10-04T11:49:47+00:00" }, { "name": "nette/robot-loader", @@ -2176,16 +2176,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.25.0.15", + "version": "6.25.0.17", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "248261ac2f7ec04fcf7cec5e1c815303368f6d0e" + "reference": "2c9cf932ab3306e0fcb90471f3b63fa3190bf7b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/248261ac2f7ec04fcf7cec5e1c815303368f6d0e", - "reference": "248261ac2f7ec04fcf7cec5e1c815303368f6d0e", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/2c9cf932ab3306e0fcb90471f3b63fa3190bf7b2", + "reference": "2c9cf932ab3306e0fcb90471f3b63fa3190bf7b2", "shasum": "" }, "require": { @@ -2242,9 +2242,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.15" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.17" }, - "time": "2024-08-26T20:22:03+00:00" + "time": "2024-08-26T20:47:13+00:00" }, { "name": "phpstan/php-8-stubs", @@ -2622,33 +2622,34 @@ }, { "name": "react/child-process", - "version": "v0.6.5", + "version": "0.7.x-dev", "source": { "type": "git", "url": "https://github.com/reactphp/child-process.git", - "reference": "e71eb1aa55f057c7a4a0d08d06b0b0a484bead43" + "reference": "ce2654d21d2a749e0a6142d00432e65ba003a2d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/child-process/zipball/e71eb1aa55f057c7a4a0d08d06b0b0a484bead43", - "reference": "e71eb1aa55f057c7a4a0d08d06b0b0a484bead43", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/ce2654d21d2a749e0a6142d00432e65ba003a2d9", + "reference": "ce2654d21d2a749e0a6142d00432e65ba003a2d9", "shasum": "" }, "require": { "evenement/evenement": "^3.0 || ^2.0 || ^1.0", "php": ">=5.3.0", "react/event-loop": "^1.2", - "react/stream": "^1.2" + "react/stream": "^1.4" }, "require-dev": { - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", - "react/socket": "^1.8", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/socket": "^1.16", "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" }, + "default-branch": true, "type": "library", "autoload": { "psr-4": { - "React\\ChildProcess\\": "src" + "React\\ChildProcess\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2685,19 +2686,15 @@ ], "support": { "issues": "https://github.com/reactphp/child-process/issues", - "source": "https://github.com/reactphp/child-process/tree/v0.6.5" + "source": "https://github.com/reactphp/child-process/tree/0.7.x" }, "funding": [ { - "url": "https://github.com/WyriHaximus", - "type": "github" - }, - { - "url": "https://github.com/clue", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2022-09-16T13:41:56+00:00" + "time": "2024-08-04T20:30:51+00:00" }, { "name": "react/dns", @@ -3013,31 +3010,31 @@ }, { "name": "react/socket", - "version": "v1.15.0", + "version": "v1.16.0", "source": { "type": "git", "url": "https://github.com/reactphp/socket.git", - "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038" + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/216d3aec0b87f04a40ca04f481e6af01bdd1d038", - "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038", + "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", "shasum": "" }, "require": { "evenement/evenement": "^3.0 || ^2.0 || ^1.0", "php": ">=5.3.0", - "react/dns": "^1.11", + "react/dns": "^1.13", "react/event-loop": "^1.2", - "react/promise": "^3 || ^2.6 || ^1.2.1", - "react/stream": "^1.2" + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" }, "require-dev": { "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/async": "^4 || ^3 || ^2", + "react/async": "^4.3 || ^3.3 || ^2", "react/promise-stream": "^1.4", - "react/promise-timer": "^1.10" + "react/promise-timer": "^1.11" }, "type": "library", "autoload": { @@ -3081,7 +3078,7 @@ ], "support": { "issues": "https://github.com/reactphp/socket/issues", - "source": "https://github.com/reactphp/socket/tree/v1.15.0" + "source": "https://github.com/reactphp/socket/tree/v1.16.0" }, "funding": [ { @@ -3089,7 +3086,7 @@ "type": "open_collective" } ], - "time": "2023-12-15T11:02:10+00:00" + "time": "2024-07-26T10:38:09+00:00" }, { "name": "react/stream", diff --git a/conf/config.neon b/conf/config.neon index 38a1518a30..4e3d04027e 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1497,6 +1497,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\HighlightStringDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\IntdivThrowTypeExtension tags: diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 2f3ef3b668..84a4006566 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -145,7 +145,7 @@ parametersSchema: minimumNumberOfJobsPerProcess: int(), buffer: int() ]) - phpVersion: schema(anyOf(schema(int(), min(70100), max(80399))), nullable()) + phpVersion: schema(anyOf(schema(int(), min(70100), max(80499))), nullable()) polluteScopeWithLoopInitialAssignments: bool() polluteScopeWithAlwaysIterableForeach: bool() propertyAlwaysWrittenTags: listOf(string()) diff --git a/patches/File.patch b/patches/File.patch new file mode 100644 index 0000000000..0732eb0e55 --- /dev/null +++ b/patches/File.patch @@ -0,0 +1,11 @@ +--- File.php 2017-07-11 09:42:15 ++++ File.php 2024-08-26 23:13:27 +@@ -192,7 +192,7 @@ + * @throws \Hoa\File\Exception\FileDoesNotExist + * @throws \Hoa\File\Exception + */ +- protected function &_open($streamName, Stream\Context $context = null) ++ protected function &_open($streamName, ?Stream\Context $context = null) + { + if (substr($streamName, 0, 4) == 'file' && + false === is_dir(dirname($streamName))) { diff --git a/patches/Idle.patch b/patches/Idle.patch new file mode 100644 index 0000000000..6f6f0dbe29 --- /dev/null +++ b/patches/Idle.patch @@ -0,0 +1,11 @@ +--- Idle.php 2017-01-16 08:53:27 ++++ Idle.php 2024-08-26 23:18:04 +@@ -100,7 +100,7 @@ + $message, + $code = 0, + $arguments = [], +- \Exception $previous = null ++ ?\Exception $previous = null + ) { + $this->_tmpArguments = $arguments; + parent::__construct($message, $code, $previous); diff --git a/patches/Invocation.patch b/patches/Invocation.patch new file mode 100644 index 0000000000..a3e9e10965 --- /dev/null +++ b/patches/Invocation.patch @@ -0,0 +1,11 @@ +--- Llk/Rule/Invocation.php 2017-08-08 09:44:07 ++++ Llk/Rule/Invocation.php 2024-08-26 23:11:25 +@@ -95,7 +95,7 @@ + public function __construct( + $rule, + $data, +- array $todo = null, ++ ?array $todo = null, + $depth = -1 + ) { + $this->_rule = $rule; diff --git a/patches/NeonParser.patch b/patches/NeonParser.patch new file mode 100644 index 0000000000..9ff7a2b3de --- /dev/null +++ b/patches/NeonParser.patch @@ -0,0 +1,11 @@ +--- src/Neon/Parser.php 2022-03-10 03:04:26 ++++ src/Neon/Parser.php 2024-08-26 21:57:02 +@@ -236,7 +236,7 @@ + } + + +- private function injectPos(Node $node, int $start = null, int $end = null): Node ++ private function injectPos(Node $node, ?int $start = null, ?int $end = null): Node + { + $node->startTokenPos = $start ?? $this->tokens->getPos(); + $node->startLine = $this->posToLine[$node->startTokenPos]; diff --git a/patches/NetteNeonStringNode.patch b/patches/NetteNeonStringNode.patch new file mode 100644 index 0000000000..ff7332693f --- /dev/null +++ b/patches/NetteNeonStringNode.patch @@ -0,0 +1,40 @@ +--- src/Neon/Node/StringNode.php 2022-03-10 03:04:26 ++++ src/Neon/Node/StringNode.php 2024-08-26 14:53:45 +@@ -79,27 +79,18 @@ + + public function toString(): string + { +- if (strpos($this->value, "\n") === false) { +- return "'" . str_replace("'", "''", $this->value) . "'"; ++ $res = json_encode($this->value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ++ if ($res === false) { ++ throw new Nette\Neon\Exception('Invalid UTF-8 sequence: ' . $this->value); ++ } + +- } elseif (preg_match('~\n[\t ]+\'{3}~', $this->value)) { +- $s = json_encode($this->value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); +- $s = preg_replace_callback( +- '#[^\\\\]|\\\\(.)#s', +- function ($m) { +- return ['n' => "\n", 't' => "\t", '"' => '"'][$m[1] ?? ''] ?? $m[0]; +- }, +- substr($s, 1, -1) +- ); +- $s = str_replace('"""', '""\"', $s); +- $delim = '"""'; +- +- } else { +- $s = $this->value; +- $delim = "'''"; ++ if (strpos($this->value, "\n") !== false) { ++ $res = preg_replace_callback('#[^\\\\]|\\\\(.)#s', function ($m) { ++ return ['n' => "\n\t", 't' => "\t", '"' => '"'][$m[1] ?? ''] ?? $m[0]; ++ }, $res); ++ $res = '"""' . "\n\t" . substr($res, 1, -1) . "\n" . '"""'; + } + +- $s = preg_replace('#^(?=.)#m', "\t", $s); +- return $delim . "\n" . $s . "\n" . $delim; ++ return $res; + } + } diff --git a/patches/Read.patch b/patches/Read.patch new file mode 100644 index 0000000000..ad9b64c445 --- /dev/null +++ b/patches/Read.patch @@ -0,0 +1,11 @@ +--- Read.php 2017-07-11 09:42:15 ++++ Read.php 2024-08-26 23:09:54 +@@ -77,7 +77,7 @@ + * @throws \Hoa\File\Exception\FileDoesNotExist + * @throws \Hoa\File\Exception + */ +- protected function &_open($streamName, Stream\Context $context = null) ++ protected function &_open($streamName, ?Stream\Context $context = null) + { + static $createModes = [ + parent::MODE_READ diff --git a/patches/Stream.patch b/patches/Stream.patch index daf6990e1b..8dbb2e108e 100644 --- a/patches/Stream.patch +++ b/patches/Stream.patch @@ -1,5 +1,5 @@ ---- Stream.php 2017-02-21 17:01:06.000000000 +0100 -+++ Stream.php 2021-04-19 17:10:20.000000000 +0200 +--- Stream.php 2024-08-26 23:05:49 ++++ Stream.php 2024-08-26 23:01:08 @@ -192,7 +192,7 @@ * @return array * @throws \Hoa\Stream\Exception @@ -9,6 +9,15 @@ $streamName, Stream $handler, $context = null +@@ -250,7 +250,7 @@ + * @return resource + * @throws \Hoa\Exception\Exception + */ +- abstract protected function &_open($streamName, Context $context = null); ++ abstract protected function &_open($streamName, ?Context $context = null); + + /** + * Close the current stream. @@ -687,11 +687,6 @@ Consistency::flexEntity('Hoa\Stream\Stream'); diff --git a/patches/TreeNode.patch b/patches/TreeNode.patch new file mode 100644 index 0000000000..39e7917def --- /dev/null +++ b/patches/TreeNode.patch @@ -0,0 +1,14 @@ +--- Llk/TreeNode.php 2017-08-08 09:44:07 ++++ Llk/TreeNode.php 2024-08-26 23:07:29 +@@ -95,9 +95,9 @@ + */ + public function __construct( + $id, +- array $value = null, ++ ?array $value = null, + array $children = [], +- self $parent = null ++ ?self $parent = null + ) { + $this->setId($id); + diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 14f81570ca..ff3f1966e2 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -338,4 +338,14 @@ public function isCurloptUrlCheckingFileSchemeWithOpenBasedir(): bool return $this->versionId < 80000; } + public function hasExitAsFunction(): bool + { + return $this->versionId >= 80400; + } + + public function highlightStringDoesNotReturnFalse(): bool + { + return $this->versionId >= 80400; + } + } diff --git a/src/Php/PhpVersionFactory.php b/src/Php/PhpVersionFactory.php index ef1e244ac4..d926420e77 100644 --- a/src/Php/PhpVersionFactory.php +++ b/src/Php/PhpVersionFactory.php @@ -26,7 +26,7 @@ public function create(): PhpVersion $parts = explode('.', $this->composerPhpVersion); $tmp = (int) $parts[0] * 10000 + (int) ($parts[1] ?? 0) * 100 + (int) ($parts[2] ?? 0); $tmp = max($tmp, 70100); - $versionId = min($tmp, 80399); + $versionId = min($tmp, 80499); $source = PhpVersion::SOURCE_COMPOSER_PLATFORM_PHP; } else { $versionId = PHP_VERSION_ID; diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index 0f8e5ba463..b25b7b1e0f 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -40,6 +40,7 @@ use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\NamespaceAnswerer; +use PHPStan\Reflection\Php\ExitFunctionReflection; use PHPStan\Reflection\Php\PhpFunctionReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\SignatureMap\NativeFunctionReflectionProvider; @@ -52,6 +53,7 @@ use function array_key_exists; use function array_map; use function base64_decode; +use function in_array; use function sprintf; use function strtolower; use const PHP_VERSION_ID; @@ -264,6 +266,12 @@ public function getFunction(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAn return $this->functionReflections[$lowerCasedFunctionName]; } + if ($this->phpVersion->hasExitAsFunction()) { + if (in_array($lowerCasedFunctionName, ['exit', 'die'], true)) { + return $this->functionReflections[$lowerCasedFunctionName] = new ExitFunctionReflection($lowerCasedFunctionName); + } + } + $nativeFunctionReflection = $this->nativeFunctionReflectionProvider->findFunctionReflection($lowerCasedFunctionName); if ($nativeFunctionReflection !== null) { $this->functionReflections[$lowerCasedFunctionName] = $nativeFunctionReflection; @@ -343,6 +351,12 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection public function resolveFunctionName(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): ?string { + if ($this->phpVersion->hasExitAsFunction()) { + $name = $nameNode->toLowerString(); + if (in_array($name, ['exit', 'die'], true)) { + return $name; + } + } return $this->resolveName($nameNode, function (string $name): bool { try { $this->reflector->reflectFunction($name); diff --git a/src/Reflection/Php/ExitFunctionReflection.php b/src/Reflection/Php/ExitFunctionReflection.php new file mode 100644 index 0000000000..e343d801b1 --- /dev/null +++ b/src/Reflection/Php/ExitFunctionReflection.php @@ -0,0 +1,140 @@ +name; + } + + public function getFileName(): ?string + { + return null; + } + + public function getVariants(): array + { + $parameterType = new UnionType([ + new StringType(), + new IntegerType(), + ]); + return [ + new FunctionVariantWithPhpDocs( + TemplateTypeMap::createEmpty(), + TemplateTypeMap::createEmpty(), + [ + new DummyParameterWithPhpDocs( + 'status', + $parameterType, + true, + PassedByReference::createNo(), + false, + new ConstantIntegerType(0), + $parameterType, + new MixedType(), + null, + TrinaryLogic::createNo(), + null, + ), + ], + false, + new NeverType(true), + new MixedType(), + new NeverType(true), + TemplateTypeVarianceMap::createEmpty(), + ), + ]; + } + + /** + * @return ParametersAcceptorWithPhpDocs[] + */ + public function getNamedArgumentsVariants(): array + { + return $this->getVariants(); + } + + public function acceptsNamedArguments(): bool + { + return true; + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getDeprecatedDescription(): ?string + { + return null; + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function getThrowType(): ?Type + { + return null; + } + + public function hasSideEffects(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function isBuiltin(): bool + { + return true; + } + + public function getAsserts(): Assertions + { + return Assertions::createEmpty(); + } + + public function getDocComment(): ?string + { + return null; + } + + public function returnsByReference(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isPure(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + +} diff --git a/src/Type/Php/HighlightStringDynamicReturnTypeExtension.php b/src/Type/Php/HighlightStringDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..12236c20cf --- /dev/null +++ b/src/Type/Php/HighlightStringDynamicReturnTypeExtension.php @@ -0,0 +1,51 @@ +getName() === 'highlight_string'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $args = $functionCall->getArgs(); + if (count($args) < 2) { + if ($this->phpVersion->highlightStringDoesNotReturnFalse()) { + return new ConstantBooleanType(true); + } + + return new BooleanType(); + } + + $returnType = $scope->getType($args[1]->value); + if ($returnType->isTrue()->yes()) { + return new StringType(); + } + + if ($this->phpVersion->highlightStringDoesNotReturnFalse()) { + return new ConstantBooleanType(true); + } + + return new BooleanType(); + } + +} diff --git a/stubs/core.stub b/stubs/core.stub index 2cb6f29448..e3a0b751ee 100644 --- a/stubs/core.stub +++ b/stubs/core.stub @@ -35,12 +35,6 @@ function highlight_file($var, bool $return = false) {} */ function show_source($var, bool $return = false) {} -/** - * @param mixed $var - * @return ($return is true ? string : bool) - */ -function highlight_string($var, bool $return = false) {} - /** * @param mixed $var * @param bool $return diff --git a/tests/PHPStan/Analyser/nsrt/bug-7341-php-84.php b/tests/PHPStan/Analyser/nsrt/bug-7341-php-84.php new file mode 100644 index 0000000000..a756e468d5 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-7341-php-84.php @@ -0,0 +1,29 @@ += 8.4 + +namespace Bug7341Php84; + +use function PHPStan\Testing\assertType; + +final class CsvWriterTerminate extends \php_user_filter +{ + /** + * @param resource $in + * @param resource $out + * @param int $consumed + * @param bool $closing + */ + public function filter($in, $out, &$consumed, $closing): int + { + while ($bucket = stream_bucket_make_writeable($in)) { + assertType('StreamBucket', $bucket); + + if (isset($this->params['terminate'])) { + $bucket->data = preg_replace('/([^\r])\n/', '$1'.$this->params['terminate'], $bucket->data); + } + $consumed += $bucket->datalen; + stream_bucket_append($out, $bucket); + } + + return \PSFS_PASS_ON; + } +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-7341.php b/tests/PHPStan/Analyser/nsrt/bug-7341.php index a227b7cf3c..45b3efe97b 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7341.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7341.php @@ -1,4 +1,4 @@ -= 8.4 + +namespace Pr1244Php84; + +use function PHPStan\Testing\assertType; + +function foo() { + /** @var string $string */ + $string = doFoo(); + + assertType('null', var_export()); + assertType('null', var_export($string)); + assertType('null', var_export($string, false)); + assertType('string', var_export($string, true)); + + assertType('true', highlight_string()); + assertType('true', highlight_string($string)); + assertType('true', highlight_string($string, false)); + assertType('string', highlight_string($string, true)); + + assertType('bool', highlight_file()); + assertType('bool', highlight_file($string)); + assertType('bool', highlight_file($string, false)); + assertType('string', highlight_file($string, true)); + + assertType('bool', show_source()); + assertType('bool', show_source($string)); + assertType('bool', show_source($string, false)); + assertType('string', show_source($string, true)); + + assertType('true', print_r()); + assertType('true', print_r($string)); + assertType('true', print_r($string, false)); + assertType('string', print_r($string, true)); +} diff --git a/tests/PHPStan/Analyser/nsrt/pr-1244.php b/tests/PHPStan/Analyser/nsrt/pr-1244.php index 6aec5018a0..a8c0fc19ae 100644 --- a/tests/PHPStan/Analyser/nsrt/pr-1244.php +++ b/tests/PHPStan/Analyser/nsrt/pr-1244.php @@ -1,4 +1,4 @@ -= 80200) { $messagePart = 'alphanumeric, backslash, or NUL'; } + if (PHP_VERSION_ID >= 80400) { + $messagePart = 'alphanumeric, backslash, or NUL byte'; + } $this->analyse( [__DIR__ . '/data/valid-regex-pattern.php'],