diff --git a/.gitignore b/.gitignore index 1abbf184..8411b3ff 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,6 @@ tests/.cache/ # committed and shipped (the deploy path has no Composer). Do not add it here. .claude/ -# Static analysis (PHPStan) -tools/phpstan/phpstan.phar +# Static analysis (PHPStan) — binary ships via committed src/vendor/ (Composer) .phpstan.cache/ src/composer.lock diff --git a/Makefile b/Makefile index efb419ae..5ef7cc8a 100644 --- a/Makefile +++ b/Makefile @@ -82,32 +82,24 @@ EXCLUDE_ARGS := $(addprefix --exclude=,$(EXCLUDES)) .PHONY: new lb main lb_copy_files main_copy_files set_permissions create_archive \ lb_archive_move main_archive_move main_install_archive clean \ delete_files_list lb_delete_files_list generate_deleted_files syntax_check \ - phpstan phpstan-install phpstan-baseline + phpstan phpstan-baseline # ─── Syntax check ─────────────────────────────────────────────── syntax_check: @bash ./tools/php_syntax_check.sh # ─── Static analysis (PHPStan) ────────────────────────────────── -PHPSTAN := tools/phpstan/phpstan.phar -PHPSTAN_VERSION := 2.1.17 - -# Download the PHPStan PHAR once (no Composer required). -phpstan-install: - @if [ ! -f "$(PHPSTAN)" ]; then \ - echo "==> Downloading PHPStan $(PHPSTAN_VERSION)"; \ - curl -fSL "https://github.com/phpstan/phpstan/releases/download/$(PHPSTAN_VERSION)/phpstan.phar" -o "$(PHPSTAN)"; \ - chmod +x "$(PHPSTAN)"; \ - else \ - echo "==> PHPStan already present: $(PHPSTAN)"; \ - fi +# PHPStan ships as a committed Composer dev dependency (src/vendor/). The +# binary auto-loads src/vendor/autoload.php, so it resolves namespaced +# third-party packages (M3uParser, PhpM3u8). No download, no `composer install`. +PHPSTAN := src/vendor/bin/phpstan # Run the analysis using phpstan.dist.neon. -phpstan: phpstan-install +phpstan: @php "$(PHPSTAN)" analyse --memory-limit=2G # Freeze all current errors into phpstan-baseline.neon (run after a level bump). -phpstan-baseline: phpstan-install +phpstan-baseline: @php "$(PHPSTAN)" analyse --memory-limit=2G --generate-baseline=phpstan-baseline.neon # ─── Generate deleted_files.txt from git diff ─────────────────── diff --git a/phpstan.dist.neon b/phpstan.dist.neon index 1b262d74..f6f8bbef 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -47,10 +47,10 @@ parameters: - src/*/tmp/* - src/tmp/* - src/backups/* - # Composer autoloader glue (committed + shipped, generated code). + # Composer autoloader glue + third-party packages (committed + + # shipped). Symbols resolve via src/vendor/autoload.php, which the + # Composer-installed phpstan binary loads automatically. - src/vendor/* - # Vendored third-party library shipped inside Core - - src/Core/Parsing/M3uParser/* # View templates: included into a controller's scope, so their # variables ($rSettings, $language, ...) are injected by the caller. # PHPStan analyses files standalone and would flag every such var as diff --git a/src/composer.json b/src/composer.json index dbda111c..94c41de8 100644 --- a/src/composer.json +++ b/src/composer.json @@ -8,6 +8,9 @@ "gemorroj/m3u-parser": "6.0.1", "chrisyue/php-m3u8": "^4" }, + "require-dev": { + "phpstan/phpstan": "2.1.17" + }, "autoload": { "psr-4": { "XcVm\\": "./" diff --git a/src/vendor/bin/phpstan b/src/vendor/bin/phpstan new file mode 100644 index 00000000..d76c0be7 --- /dev/null +++ b/src/vendor/bin/phpstan @@ -0,0 +1,119 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpstan/phpstan/phpstan'); + } +} + +return include __DIR__ . '/..'.'/phpstan/phpstan/phpstan'; diff --git a/src/vendor/bin/phpstan.phar b/src/vendor/bin/phpstan.phar new file mode 100644 index 00000000..fecf96f6 --- /dev/null +++ b/src/vendor/bin/phpstan.phar @@ -0,0 +1,119 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpstan/phpstan/phpstan.phar'); + } +} + +return include __DIR__ . '/..'.'/phpstan/phpstan/phpstan.phar'; diff --git a/src/vendor/composer/autoload_files.php b/src/vendor/composer/autoload_files.php new file mode 100644 index 00000000..b62e293b --- /dev/null +++ b/src/vendor/composer/autoload_files.php @@ -0,0 +1,10 @@ + $vendorDir . '/phpstan/phpstan/bootstrap.php', +); diff --git a/src/vendor/composer/autoload_real.php b/src/vendor/composer/autoload_real.php index c70beb58..d15992d7 100644 --- a/src/vendor/composer/autoload_real.php +++ b/src/vendor/composer/autoload_real.php @@ -33,6 +33,18 @@ class ComposerAutoloaderInit80ff86aa3df258be188ff24c30fbd1e8 $loader->register(true); + $filesToLoad = \Composer\Autoload\ComposerStaticInit80ff86aa3df258be188ff24c30fbd1e8::$files; + $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } + }, null, null); + foreach ($filesToLoad as $fileIdentifier => $file) { + $requireFile($fileIdentifier, $file); + } + return $loader; } } diff --git a/src/vendor/composer/autoload_static.php b/src/vendor/composer/autoload_static.php index 92a3c590..f1a68be2 100644 --- a/src/vendor/composer/autoload_static.php +++ b/src/vendor/composer/autoload_static.php @@ -6,6 +6,10 @@ namespace Composer\Autoload; class ComposerStaticInit80ff86aa3df258be188ff24c30fbd1e8 { + public static $files = array ( + '9b38cf48e83f5d8f60375221cd213eee' => __DIR__ . '/..' . '/phpstan/phpstan/bootstrap.php', + ); + public static $prefixLengthsPsr4 = array ( 'X' => array ( diff --git a/src/vendor/composer/installed.json b/src/vendor/composer/installed.json index 57641acf..2bd3cef8 100644 --- a/src/vendor/composer/installed.json +++ b/src/vendor/composer/installed.json @@ -105,8 +105,71 @@ "source": "https://github.com/Gemorroj/M3uParser/tree/6.0.1" }, "install-path": "../gemorroj/m3u-parser" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.17", + "version_normalized": "2.1.17.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/89b5ef665716fa2a52ecd2633f21007a6a349053", + "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "time": "2025-05-21T20:55:28+00:00", + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "install-path": "../phpstan/phpstan" } ], "dev": true, - "dev-package-names": [] + "dev-package-names": [ + "phpstan/phpstan" + ] } diff --git a/src/vendor/composer/installed.php b/src/vendor/composer/installed.php index 276f0778..71ae794c 100644 --- a/src/vendor/composer/installed.php +++ b/src/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'vateron-media/xc-vm', 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => '605b59d435526586b2b27756fb5644e1f7a2fe34', + 'reference' => 'cf3901e0854eb1d20f6d8b2341f7613e93eb1beb', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -28,10 +28,19 @@ 'aliases' => array(), 'dev_requirement' => false, ), + 'phpstan/phpstan' => array( + 'pretty_version' => '2.1.17', + 'version' => '2.1.17.0', + 'reference' => '89b5ef665716fa2a52ecd2633f21007a6a349053', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpstan/phpstan', + 'aliases' => array(), + 'dev_requirement' => true, + ), 'vateron-media/xc-vm' => array( 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => '605b59d435526586b2b27756fb5644e1f7a2fe34', + 'reference' => 'cf3901e0854eb1d20f6d8b2341f7613e93eb1beb', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), diff --git a/src/vendor/phpstan/phpstan/LICENSE b/src/vendor/phpstan/phpstan/LICENSE new file mode 100644 index 00000000..e5f34e60 --- /dev/null +++ b/src/vendor/phpstan/phpstan/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2016 Ondřej Mirtes +Copyright (c) 2025 PHPStan s.r.o. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/vendor/phpstan/phpstan/README.md b/src/vendor/phpstan/phpstan/README.md new file mode 100644 index 00000000..abae67ec --- /dev/null +++ b/src/vendor/phpstan/phpstan/README.md @@ -0,0 +1,108 @@ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[**You can now sponsor my open-source work on PHPStan through GitHub Sponsors.**](https://github.com/sponsors/ondrejmirtes)
+
+Does GitHub already have your 💳? Do you use PHPStan to find 🐛 before they reach production? [Send a couple of 💸 a month my way too.](https://github.com/sponsors/ondrejmirtes) Thank you!
+
+One-time donations [through Revolut.me](https://revolut.me/ondrejmirtes) are also accepted. To request an invoice, [contact me](mailto:ondrej@mirtes.cz) through e-mail.
+
+## Documentation
+
+All the documentation lives on the [phpstan.org website](https://phpstan.org/):
+
+* [Getting Started & User Guide](https://phpstan.org/user-guide/getting-started)
+* [Config Reference](https://phpstan.org/config-reference)
+* [PHPDocs Basics](https://phpstan.org/writing-php-code/phpdocs-basics) & [PHPDoc Types](https://phpstan.org/writing-php-code/phpdoc-types)
+* [Extension Library](https://phpstan.org/user-guide/extension-library)
+* [Developing Extensions](https://phpstan.org/developing-extensions/extension-types)
+* [API Reference](https://apiref.phpstan.org/)
+
+## PHPStan Pro
+
+PHPStan Pro is a paid add-on on top of open-source PHPStan Static Analysis Tool with these premium features:
+
+* Web UI for browsing found errors, you can click and open your editor of choice on the offending line.
+* Continuous analysis (watch mode): scans changed files in the background, refreshes the UI automatically.
+
+Try it on PHPStan 0.12.45 or later by running it with the `--pro` option. You can create an account either by following the on-screen instructions, or by visiting [account.phpstan.com](https://account.phpstan.com/).
+
+After 30-day free trial period it costs 7 EUR for individuals monthly, 70 EUR for teams (up to 25 members). By paying for PHPStan Pro, you're supporting the development of open-source PHPStan.
+
+You can read more about it on [PHPStan's website](https://phpstan.org/blog/introducing-phpstan-pro).
+
+## Code of Conduct
+
+This project adheres to a [Contributor Code of Conduct](https://github.com/phpstan/phpstan/blob/master/CODE_OF_CONDUCT.md). By participating in this project and its community, you are expected to uphold this code.
+
+## Contributing
+
+Any contributions are welcome. PHPStan's source code open to pull requests lives at [`phpstan/phpstan-src`](https://github.com/phpstan/phpstan-src).
diff --git a/src/vendor/phpstan/phpstan/UPGRADING.md b/src/vendor/phpstan/phpstan/UPGRADING.md
new file mode 100644
index 00000000..52294909
--- /dev/null
+++ b/src/vendor/phpstan/phpstan/UPGRADING.md
@@ -0,0 +1,338 @@
+Upgrading from PHPStan 1.x to 2.0
+=================================
+
+## PHP version requirements
+
+PHPStan now requires PHP 7.4 or newer to run.
+
+## Upgrading guide for end users
+
+The best way to get ready for upgrade to PHPStan 2.0 is to update to the **latest PHPStan 1.12 release**
+and enable [**Bleeding Edge**](https://phpstan.org/blog/what-is-bleeding-edge). This will enable the new rules and behaviours that 2.0 turns on for all users.
+
+Also make sure to install and enable [`phpstan/phpstan-deprecation-rules`](https://github.com/phpstan/phpstan-deprecation-rules).
+
+Once you get to a green build with no deprecations showed on latest PHPStan 1.12.x with Bleeding Edge enabled, you can update all your related PHPStan dependencies to 2.0 in `composer.json`:
+
+```json
+"require-dev": {
+ "phpstan/phpstan": "^2.0",
+ "phpstan/phpstan-deprecation-rules": "^2.0",
+ "phpstan/phpstan-doctrine": "^2.0",
+ "phpstan/phpstan-nette": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpstan/phpstan-strict-rules": "^2.0",
+ "phpstan/phpstan-symfony": "^2.0",
+ "phpstan/phpstan-webmozart-assert": "^2.0",
+ ...
+}
+```
+
+Don't forget to update [3rd party PHPStan extensions](https://phpstan.org/user-guide/extension-library) as well.
+
+After changing your `composer.json`, run `composer update 'phpstan/*' -W`.
+
+It's up to you whether you go through the new reported errors or if you just put them all to the [baseline](https://phpstan.org/user-guide/baseline) ;) Everyone who's on PHPStan 1.12 should be able to upgrade to PHPStan 2.0.
+
+### Noteworthy changes to code analysis
+
+* [**Enhancements in handling parameters passed by reference**](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference)
+* [**Validate inline PHPDoc `@var` tag type**](https://phpstan.org/blog/phpstan-1-10-comes-with-lie-detector#validate-inline-phpdoc-%40var-tag-type)
+* [**List type enforced**](https://phpstan.org/blog/phpstan-1-9-0-with-phpdoc-asserts-list-type#list-type)
+* **Always `true` conditions always reported**: previously reported only with phpstan-strict-rules, this is now always reported.
+
+### Removed option `checkMissingIterableValueType`
+
+It's strongly recommended to add the missing array typehints.
+
+If you want to continue ignoring missing typehints from arrays, add `missingType.iterableValue` error identifier to your `ignoreErrors`:
+
+```neon
+parameters:
+ ignoreErrors:
+ -
+ identifier: missingType.iterableValue
+```
+
+### Removed option `checkGenericClassInNonGenericObjectType`
+
+It's strongly recommended to add the missing generic typehints.
+
+If you want to continue ignoring missing typehints from generics, add `missingType.generics` error identifier to your `ignoreErrors`:
+
+```neon
+parameters:
+ ignoreErrors:
+ -
+ identifier: missingType.generics
+```
+
+### Removed `checkAlwaysTrue*` options
+
+These options have been removed because PHPStan now always behaves as if these were set to `true`:
+
+* `checkAlwaysTrueCheckTypeFunctionCall`
+* `checkAlwaysTrueInstanceof`
+* `checkAlwaysTrueStrictComparison`
+* `checkAlwaysTrueLooseComparison`
+
+### Removed option `excludes_analyse`
+
+It has been replaced with [`excludePaths`](https://phpstan.org/user-guide/ignoring-errors#excluding-whole-files).
+
+### Paths in `excludePaths` and `ignoreErrors` have to be a valid file path or a fnmatch pattern
+
+If you are excluding a file path that might not exist but you still want to have it in `excludePaths`, append `(?)`:
+
+```neon
+parameters:
+ excludePaths:
+ - tests/*/data/*
+ - src/broken
+ - node_modules (?) # optional path, might not exist
+```
+
+If you have the same situation in `ignoreErrors` (ignoring an error in a path that might not exist), use `reportUnmatchedIgnoredErrors: false`.
+
+```neon
+parameters:
+ reportUnmatchedIgnoredErrors: false
+```
+
+Appending `(?)` in `ignoreErrors` is not supported.
+
+### Changes in 1st party PHPStan extensions
+
+* [phpstan-doctrine](https://github.com/phpstan/phpstan-doctrine)
+ * Removed config parameter `searchOtherMethodsForQueryBuilderBeginning` (extension now behaves as when this was set to `true`)
+ * Removed config parameter `queryBuilderFastAlgorithm` (extension now behaves as when this was set to `false`)
+* [phpstan-symfony](https://github.com/phpstan/phpstan-symfony)
+ * Removed legacy options with `_` in the name
+ * `container_xml_path` -> use `containerXmlPath`
+ * `constant_hassers` -> use `constantHassers`
+ * `console_application_loader` -> use `consoleApplicationLoader`
+
+### Minor backward compatibility breaks
+
+* Removed unused config parameter `cache.nodesByFileCountMax`
+* Removed unused config parameter `memoryLimitFile`
+* Removed unused feature toggle `disableRuntimeReflectionProvider`
+* Removed unused config parameter `staticReflectionClassNamePatterns`
+* Remove `fixerTmpDir` config parameter, use `pro.tmpDir` instead
+* Remove `tempResultCachePath` config parameter, use `resultCachePath` instead
+* `additionalConfigFiles` config parameter must be a list
+
+## Upgrading guide for extension developers
+
+> [!NOTE]
+> Please switch to PHPStan 2.0 in a new major version of your extension. It's not feasible to try to support both PHPStan 1.x and PHPStan 2.x with the same extension code.
+>
+> You can definitely get closer to supporting PHPStan 2.0 without increasing major version by solving reported deprecations and other issues by analysing your extension code with PHPStan & phpstan-deprecation-rules & Bleeding Edge, but the final leap and solving backward incompatibilities should be done by requiring `"phpstan/phpstan": "^2.0"` in your `composer.json`, and releasing a new major version.
+
+### PHPStan now uses nikic/php-parser v5
+
+See [UPGRADING](https://github.com/nikic/PHP-Parser/blob/master/UPGRADE-5.0.md) guide for PHP-Parser.
+
+The most notable change is how `throw` statement is represented. Previously, `throw` statements like `throw $e;` were represented using the `Stmt\Throw_` class, while uses inside other expressions (such as `$x ?? throw $e`) used the `Expr\Throw_` class.
+
+Now, `throw $e;` is represented as a `Stmt\Expression` that contains an `Expr\Throw_`. The
+`Stmt\Throw_` class has been removed.
+
+### PHPStan now uses phpstan/phpdoc-parser v2
+
+See [UPGRADING](https://github.com/phpstan/phpdoc-parser/blob/2.0.x/UPGRADING.md) guide for phpstan/phpdoc-parser.
+
+### Returning plain strings as errors no longer supported, use RuleErrorBuilder
+
+Identifiers are also required in custom rules.
+
+Learn more: [Using RuleErrorBuilder to enrich reported errors in custom rules](https://phpstan.org/blog/using-rule-error-builder)
+
+**Before**:
+
+```php
+return ['My error'];
+```
+
+**After**:
+
+```php
+return [
+ RuleErrorBuilder::message('My error')
+ ->identifier('my.error')
+ ->build(),
+];
+```
+
+### Deprecate various `instanceof *Type` in favour of new methods on `Type` interface
+
+Learn more: [Why Is instanceof *Type Wrong and Getting Deprecated?](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated)
+
+### Removed deprecated `ParametersAcceptorSelector::selectSingle()`
+
+Use [`ParametersAcceptorSelector::selectFromArgs()`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ParametersAcceptorSelector.html#_selectFromArgs) instead. It should be used in most places where `selectSingle()` was previously used, like dynamic return type extensions.
+
+**Before**:
+
+```php
+$defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
+```
+
+**After**:
+
+```php
+$defaultReturnType = ParametersAcceptorSelector::selectFromArgs(
+ $scope,
+ $functionCall->getArgs(),
+ $functionReflection->getVariants()
+)->getReturnType();
+```
+
+If you're analysing function or method body itself and you're using one of the following methods, ask for `getParameters()` and `getReturnType()` directly on the reflection object:
+
+* [InClassMethodNode::getMethodReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.InClassMethodNode.html)
+* [InFunctionNode::getFunctionReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.InFunctionNode.html)
+* [FunctionReturnStatementsNode::getFunctionReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.FunctionReturnStatementsNode.html)
+* [MethodReturnStatementsNode::getMethodReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.MethodReturnStatementsNode.html)
+* [Scope::getFunction()](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.Scope.html#_getFunction)
+
+**Before**:
+
+```php
+$function = $node->getFunctionReflection();
+$returnType = ParametersAcceptorSelector::selectSingle($function->getVariants())->getReturnType();
+```
+
+**After**:
+
+```php
+$returnType = $node->getFunctionReflection()->getReturnType();
+```
+
+### Changed `TypeSpecifier::create()` and `SpecifiedTypes` constructor parameters
+
+[`PHPStan\Analyser\TypeSpecifier::create()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.TypeSpecifier.html#_create) now accepts (all parameters are required):
+
+* `Expr $expr`
+* `Type $type`
+* `TypeSpecifierContext $context`
+* `Scope $scope`
+
+If you want to change `$overwrite` or `$rootExpr` (previous parameters also used to be accepted by this method), call `setAlwaysOverwriteTypes()` and `setRootExpr()` on [`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) (object returned by `TypeSpecifier::create()`). These methods return a new object (SpecifiedTypes is immutable).
+
+[`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) constructor now accepts:
+
+* `array $sureTypes`
+* `array $sureNotTypes`
+
+If you want to change `$overwrite` or `$rootExpr` (previous parameters also used to be accepted by the constructor), call `setAlwaysOverwriteTypes()` and `setRootExpr()`. These methods return a new object (SpecifiedTypes is immutable).
+
+### `ConstantArrayType` no longer extends `ArrayType`
+
+`Type::getArrays()` now returns `list