From 904ff28f1ed2d258d3c4ff129f8311c03919d67e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 13 Mar 2024 15:02:20 +0100 Subject: [PATCH 01/21] Utilize composer min-php version to narrow PHP_VERSION_ID --- .github/workflows/e2e-tests.yml | 29 ++++ conf/config.neon | 10 +- conf/parametersSchema.neon | 8 +- e2e/composer-max-version/.gitignore | 2 + e2e/composer-max-version/composer.json | 5 + e2e/composer-max-version/phpstan.neon | 2 + e2e/composer-max-version/test.php | 10 ++ e2e/composer-min-max-version/.gitignore | 2 + e2e/composer-min-max-version/composer.json | 5 + e2e/composer-min-max-version/phpstan.neon | 2 + e2e/composer-min-max-version/test.php | 10 ++ e2e/composer-min-open-end-version/.gitignore | 2 + .../composer.json | 5 + .../phpstan.neon | 2 + e2e/composer-min-open-end-version/test.php | 6 + e2e/composer-min-version-bc/.gitignore | 2 + e2e/composer-min-version-bc/composer.json | 5 + e2e/composer-min-version-bc/test.php | 7 + e2e/composer-min-version/.gitignore | 2 + e2e/composer-min-version/composer.json | 5 + e2e/composer-min-version/phpstan.neon | 2 + e2e/composer-min-version/test.php | 6 + .../phpstan.neon | 7 + e2e/composer-version-config-patch/.gitignore | 2 + .../composer.json | 5 + .../phpstan.neon | 2 + e2e/composer-version-config-patch/test.php | 7 + e2e/composer-version-config/.gitignore | 2 + e2e/composer-version-config/composer.json | 5 + e2e/composer-version-config/phpstan.neon | 6 + e2e/composer-version-config/test.php | 11 ++ src/Analyser/ConstantResolver.php | 69 +++++++++- src/Analyser/ConstantResolverFactory.php | 5 + src/DependencyInjection/ContainerFactory.php | 13 ++ .../InvalidPhpVersionException.php | 10 ++ .../ValidateIgnoredErrorsExtension.php | 2 +- src/Php/ComposerPhpVersionFactory.php | 125 ++++++++++++++++++ src/Php/PhpVersion.php | 21 ++- src/Php/PhpVersionFactoryFactory.php | 18 ++- src/Testing/PHPStanTestCase.php | 2 +- ...pareFunctionDynamicReturnTypeExtension.php | 38 +++++- 41 files changed, 460 insertions(+), 19 deletions(-) create mode 100644 e2e/composer-max-version/.gitignore create mode 100644 e2e/composer-max-version/composer.json create mode 100644 e2e/composer-max-version/phpstan.neon create mode 100644 e2e/composer-max-version/test.php create mode 100644 e2e/composer-min-max-version/.gitignore create mode 100644 e2e/composer-min-max-version/composer.json create mode 100644 e2e/composer-min-max-version/phpstan.neon create mode 100644 e2e/composer-min-max-version/test.php create mode 100644 e2e/composer-min-open-end-version/.gitignore create mode 100644 e2e/composer-min-open-end-version/composer.json create mode 100644 e2e/composer-min-open-end-version/phpstan.neon create mode 100644 e2e/composer-min-open-end-version/test.php create mode 100644 e2e/composer-min-version-bc/.gitignore create mode 100644 e2e/composer-min-version-bc/composer.json create mode 100644 e2e/composer-min-version-bc/test.php create mode 100644 e2e/composer-min-version/.gitignore create mode 100644 e2e/composer-min-version/composer.json create mode 100644 e2e/composer-min-version/phpstan.neon create mode 100644 e2e/composer-min-version/test.php create mode 100644 e2e/composer-version-config-invalid/phpstan.neon create mode 100644 e2e/composer-version-config-patch/.gitignore create mode 100644 e2e/composer-version-config-patch/composer.json create mode 100644 e2e/composer-version-config-patch/phpstan.neon create mode 100644 e2e/composer-version-config-patch/test.php create mode 100644 e2e/composer-version-config/.gitignore create mode 100644 e2e/composer-version-config/composer.json create mode 100644 e2e/composer-version-config/phpstan.neon create mode 100644 e2e/composer-version-config/test.php create mode 100644 src/DependencyInjection/InvalidPhpVersionException.php create mode 100644 src/Php/ComposerPhpVersionFactory.php diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 9c134d199c..517086cb68 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -292,6 +292,35 @@ jobs: - script: | cd e2e/bug-11819 ../../bin/phpstan + - script: | + cd e2e/composer-max-version + composer install + ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/composer-min-max-version + composer install + ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/composer-min-open-end-version + composer install + ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/composer-min-version-bc + composer install + ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/composer-min-version + composer install + ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/composer-version-config + composer install + ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/composer-version-config-invalid + OUTPUT=$(../../bin/phpstan 2>&1 || true) + grep 'Invalid configuration' <<< "$OUTPUT" + grep 'Invalid PHP version range: phpVersion.max should be greater or equal to phpVersion.min.' <<< "$OUTPUT" steps: - name: "Checkout" diff --git a/conf/config.neon b/conf/config.neon index 7d1bf16616..676acf01ab 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -339,8 +339,16 @@ services: - class: PHPStan\Php\PhpVersionFactoryFactory arguments: - versionId: %phpVersion% + phpVersion: %phpVersion% composerAutoloaderProjectPaths: %composerAutoloaderProjectPaths% + bleedingEdge: %featureToggles.bleedingEdge% + + - + class: PHPStan\Php\ComposerPhpVersionFactory + arguments: + phpVersion: %phpVersion% + composerAutoloaderProjectPaths: %composerAutoloaderProjectPaths% + bleedingEdge: %featureToggles.bleedingEdge% - class: PHPStan\PhpDocParser\ParserConfig diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index d6e4a34882..6c7f9c40e6 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -79,7 +79,13 @@ parametersSchema: minimumNumberOfJobsPerProcess: int(), buffer: int() ]) - phpVersion: schema(anyOf(schema(int(), min(70100), max(80499))), nullable()) + phpVersion: schema(anyOf( + schema(int(), min(70100), max(80499)), + structure([ + min: schema(int(), min(70100), max(80499)), + max: schema(int(), min(70100), max(80499)) + ]) + ), nullable()) polluteScopeWithLoopInitialAssignments: bool() polluteScopeWithAlwaysIterableForeach: bool() polluteScopeWithBlock: bool() diff --git a/e2e/composer-max-version/.gitignore b/e2e/composer-max-version/.gitignore new file mode 100644 index 0000000000..3a9875b460 --- /dev/null +++ b/e2e/composer-max-version/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-max-version/composer.json b/e2e/composer-max-version/composer.json new file mode 100644 index 0000000000..4d4ca141ef --- /dev/null +++ b/e2e/composer-max-version/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": "<=8.3" + } +} diff --git a/e2e/composer-max-version/phpstan.neon b/e2e/composer-max-version/phpstan.neon new file mode 100644 index 0000000000..49d50a5bac --- /dev/null +++ b/e2e/composer-max-version/phpstan.neon @@ -0,0 +1,2 @@ +includes: + - ../../conf/bleedingEdge.neon diff --git a/e2e/composer-max-version/test.php b/e2e/composer-max-version/test.php new file mode 100644 index 0000000000..038f559122 --- /dev/null +++ b/e2e/composer-max-version/test.php @@ -0,0 +1,10 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('int<5, 8>', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); + +\PHPStan\Testing\assertType('-1|0|1', version_compare(PHP_VERSION, '7.0.0')); +\PHPStan\Testing\assertType('bool', version_compare(PHP_VERSION, '7.0.0', '<')); +\PHPStan\Testing\assertType('bool', version_compare(PHP_VERSION, '7.0.0', '>')); diff --git a/e2e/composer-min-max-version/.gitignore b/e2e/composer-min-max-version/.gitignore new file mode 100644 index 0000000000..3a9875b460 --- /dev/null +++ b/e2e/composer-min-max-version/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-min-max-version/composer.json b/e2e/composer-min-max-version/composer.json new file mode 100644 index 0000000000..869fd2ce42 --- /dev/null +++ b/e2e/composer-min-max-version/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": ">=8.1, <=8.2.99" + } +} diff --git a/e2e/composer-min-max-version/phpstan.neon b/e2e/composer-min-max-version/phpstan.neon new file mode 100644 index 0000000000..49d50a5bac --- /dev/null +++ b/e2e/composer-min-max-version/phpstan.neon @@ -0,0 +1,2 @@ +includes: + - ../../conf/bleedingEdge.neon diff --git a/e2e/composer-min-max-version/test.php b/e2e/composer-min-max-version/test.php new file mode 100644 index 0000000000..28d770f3bb --- /dev/null +++ b/e2e/composer-min-max-version/test.php @@ -0,0 +1,10 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('8', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<1, 2>', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); + +\PHPStan\Testing\assertType('1', version_compare(PHP_VERSION, '7.0.0')); +\PHPStan\Testing\assertType('false', version_compare(PHP_VERSION, '7.0.0', '<')); +\PHPStan\Testing\assertType('true', version_compare(PHP_VERSION, '7.0.0', '>')); diff --git a/e2e/composer-min-open-end-version/.gitignore b/e2e/composer-min-open-end-version/.gitignore new file mode 100644 index 0000000000..3a9875b460 --- /dev/null +++ b/e2e/composer-min-open-end-version/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-min-open-end-version/composer.json b/e2e/composer-min-open-end-version/composer.json new file mode 100644 index 0000000000..b6303c6b77 --- /dev/null +++ b/e2e/composer-min-open-end-version/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": ">= 8.1" + } +} diff --git a/e2e/composer-min-open-end-version/phpstan.neon b/e2e/composer-min-open-end-version/phpstan.neon new file mode 100644 index 0000000000..49d50a5bac --- /dev/null +++ b/e2e/composer-min-open-end-version/phpstan.neon @@ -0,0 +1,2 @@ +includes: + - ../../conf/bleedingEdge.neon diff --git a/e2e/composer-min-open-end-version/test.php b/e2e/composer-min-open-end-version/test.php new file mode 100644 index 0000000000..d35bd6bca0 --- /dev/null +++ b/e2e/composer-min-open-end-version/test.php @@ -0,0 +1,6 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('int<8, max>', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); diff --git a/e2e/composer-min-version-bc/.gitignore b/e2e/composer-min-version-bc/.gitignore new file mode 100644 index 0000000000..3a9875b460 --- /dev/null +++ b/e2e/composer-min-version-bc/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-min-version-bc/composer.json b/e2e/composer-min-version-bc/composer.json new file mode 100644 index 0000000000..9be64619f1 --- /dev/null +++ b/e2e/composer-min-version-bc/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": "^8.1" + } +} diff --git a/e2e/composer-min-version-bc/test.php b/e2e/composer-min-version-bc/test.php new file mode 100644 index 0000000000..7a04552626 --- /dev/null +++ b/e2e/composer-min-version-bc/test.php @@ -0,0 +1,7 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('int<5, max>', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); diff --git a/e2e/composer-min-version/.gitignore b/e2e/composer-min-version/.gitignore new file mode 100644 index 0000000000..3a9875b460 --- /dev/null +++ b/e2e/composer-min-version/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-min-version/composer.json b/e2e/composer-min-version/composer.json new file mode 100644 index 0000000000..9be64619f1 --- /dev/null +++ b/e2e/composer-min-version/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": "^8.1" + } +} diff --git a/e2e/composer-min-version/phpstan.neon b/e2e/composer-min-version/phpstan.neon new file mode 100644 index 0000000000..49d50a5bac --- /dev/null +++ b/e2e/composer-min-version/phpstan.neon @@ -0,0 +1,2 @@ +includes: + - ../../conf/bleedingEdge.neon diff --git a/e2e/composer-min-version/test.php b/e2e/composer-min-version/test.php new file mode 100644 index 0000000000..a88c78b607 --- /dev/null +++ b/e2e/composer-min-version/test.php @@ -0,0 +1,6 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('int<8, 9>', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); diff --git a/e2e/composer-version-config-invalid/phpstan.neon b/e2e/composer-version-config-invalid/phpstan.neon new file mode 100644 index 0000000000..71a6bb7c5a --- /dev/null +++ b/e2e/composer-version-config-invalid/phpstan.neon @@ -0,0 +1,7 @@ +includes: + - ../../conf/bleedingEdge.neon +parameters: + phpVersion: + min: 80303 + max: 80104 + diff --git a/e2e/composer-version-config-patch/.gitignore b/e2e/composer-version-config-patch/.gitignore new file mode 100644 index 0000000000..3a9875b460 --- /dev/null +++ b/e2e/composer-version-config-patch/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-version-config-patch/composer.json b/e2e/composer-version-config-patch/composer.json new file mode 100644 index 0000000000..d6103988c8 --- /dev/null +++ b/e2e/composer-version-config-patch/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": ">=8.0.2, <8.0.15" + } +} diff --git a/e2e/composer-version-config-patch/phpstan.neon b/e2e/composer-version-config-patch/phpstan.neon new file mode 100644 index 0000000000..49d50a5bac --- /dev/null +++ b/e2e/composer-version-config-patch/phpstan.neon @@ -0,0 +1,2 @@ +includes: + - ../../conf/bleedingEdge.neon diff --git a/e2e/composer-version-config-patch/test.php b/e2e/composer-version-config-patch/test.php new file mode 100644 index 0000000000..fc3e9997ce --- /dev/null +++ b/e2e/composer-version-config-patch/test.php @@ -0,0 +1,7 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('8', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('0', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<1, 15>', PHP_RELEASE_VERSION); diff --git a/e2e/composer-version-config/.gitignore b/e2e/composer-version-config/.gitignore new file mode 100644 index 0000000000..3a9875b460 --- /dev/null +++ b/e2e/composer-version-config/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-version-config/composer.json b/e2e/composer-version-config/composer.json new file mode 100644 index 0000000000..2da0adaf1c --- /dev/null +++ b/e2e/composer-version-config/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": "^8.0" + } +} diff --git a/e2e/composer-version-config/phpstan.neon b/e2e/composer-version-config/phpstan.neon new file mode 100644 index 0000000000..04365f4376 --- /dev/null +++ b/e2e/composer-version-config/phpstan.neon @@ -0,0 +1,6 @@ +includes: + - ../../conf/bleedingEdge.neon +parameters: + phpVersion: + min: 80103 + max: 80304 diff --git a/e2e/composer-version-config/test.php b/e2e/composer-version-config/test.php new file mode 100644 index 0000000000..a9afaa4b65 --- /dev/null +++ b/e2e/composer-version-config/test.php @@ -0,0 +1,11 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('8', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<1, 3>', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); + +\PHPStan\Testing\assertType('1', version_compare(PHP_VERSION, '7.0.0')); +\PHPStan\Testing\assertType('false', version_compare(PHP_VERSION, '7.0.0', '<')); +\PHPStan\Testing\assertType('true', version_compare(PHP_VERSION, '7.0.0', '>')); diff --git a/src/Analyser/ConstantResolver.php b/src/Analyser/ConstantResolver.php index b209533fac..6b73db97e4 100644 --- a/src/Analyser/ConstantResolver.php +++ b/src/Analyser/ConstantResolver.php @@ -3,6 +3,7 @@ namespace PHPStan\Analyser; use PhpParser\Node\Name; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\NamespaceAnswerer; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider; @@ -20,6 +21,7 @@ use PHPStan\Type\UnionType; use function array_key_exists; use function in_array; +use function max; use function sprintf; use const INF; use const NAN; @@ -34,7 +36,12 @@ final class ConstantResolver /** * @param string[] $dynamicConstantNames */ - public function __construct(private ReflectionProviderProvider $reflectionProviderProvider, private array $dynamicConstantNames) + public function __construct( + private ReflectionProviderProvider $reflectionProviderProvider, + private array $dynamicConstantNames, + private ?PhpVersion $composerMinPhpVersion, + private ?PhpVersion $composerMaxPhpVersion, + ) { } @@ -77,16 +84,60 @@ public function resolvePredefinedConstant(string $resolvedConstantName): ?Type ]); } if ($resolvedConstantName === 'PHP_MAJOR_VERSION') { - return IntegerRangeType::fromInterval(5, null); + $minMajor = 5; + $maxMajor = null; + + if ($this->composerMinPhpVersion !== null) { + $minMajor = max($minMajor, $this->composerMinPhpVersion->getMajor()); + } + if ($this->composerMaxPhpVersion !== null) { + $maxMajor = $this->composerMaxPhpVersion->getMajor(); + } + + return $this->createInteger($minMajor, $maxMajor); } if ($resolvedConstantName === 'PHP_MINOR_VERSION') { - return IntegerRangeType::fromInterval(0, null); + $minMinor = 0; + $maxMinor = null; + + if ( + $this->composerMinPhpVersion !== null + && $this->composerMaxPhpVersion !== null + && $this->composerMaxPhpVersion->getMajor() === $this->composerMinPhpVersion->getMajor() + ) { + $minMinor = $this->composerMinPhpVersion->getMinor(); + $maxMinor = $this->composerMaxPhpVersion->getMinor(); + } + + return $this->createInteger($minMinor, $maxMinor); } if ($resolvedConstantName === 'PHP_RELEASE_VERSION') { - return IntegerRangeType::fromInterval(0, null); + $minRelease = 0; + $maxRelease = null; + + if ( + $this->composerMinPhpVersion !== null + && $this->composerMaxPhpVersion !== null + && $this->composerMaxPhpVersion->getMajor() === $this->composerMinPhpVersion->getMajor() + && $this->composerMaxPhpVersion->getMinor() === $this->composerMinPhpVersion->getMinor() + ) { + $minRelease = $this->composerMinPhpVersion->getPatch(); + $maxRelease = $this->composerMaxPhpVersion->getPatch(); + } + + return $this->createInteger($minRelease, $maxRelease); } if ($resolvedConstantName === 'PHP_VERSION_ID') { - return IntegerRangeType::fromInterval(50207, null); + $minVersion = 50207; + $maxVersion = null; + if ($this->composerMinPhpVersion !== null) { + $minVersion = max($minVersion, $this->composerMinPhpVersion->getVersionId()); + } + if ($this->composerMaxPhpVersion !== null) { + $maxVersion = $this->composerMaxPhpVersion->getVersionId(); + } + + return $this->createInteger($minVersion, $maxVersion); } if ($resolvedConstantName === 'PHP_ZTS') { return new UnionType([ @@ -325,6 +376,14 @@ public function resolveClassConstantType(string $className, string $constantName return $constantType; } + private function createInteger(?int $min, ?int $max): Type + { + if ($min !== null && $min === $max) { + return new ConstantIntegerType($min); + } + return IntegerRangeType::fromInterval($min, $max); + } + private function getReflectionProvider(): ReflectionProvider { return $this->reflectionProviderProvider->getReflectionProvider(); diff --git a/src/Analyser/ConstantResolverFactory.php b/src/Analyser/ConstantResolverFactory.php index 67a98408a0..f111da14ec 100644 --- a/src/Analyser/ConstantResolverFactory.php +++ b/src/Analyser/ConstantResolverFactory.php @@ -3,6 +3,7 @@ namespace PHPStan\Analyser; use PHPStan\DependencyInjection\Container; +use PHPStan\Php\ComposerPhpVersionFactory; use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider; final class ConstantResolverFactory @@ -17,9 +18,13 @@ public function __construct( public function create(): ConstantResolver { + $composerFactory = $this->container->getByType(ComposerPhpVersionFactory::class); + return new ConstantResolver( $this->reflectionProviderProvider, $this->container->getParameter('dynamicConstantNames'), + $composerFactory->getMinVersion(), + $composerFactory->getMaxVersion(), ); } diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php index 7ac72d4fc4..75c79d6678 100644 --- a/src/DependencyInjection/ContainerFactory.php +++ b/src/DependencyInjection/ContainerFactory.php @@ -32,6 +32,7 @@ use PHPStan\ShouldNotHappenException; use PHPStan\Type\ObjectType; use function array_diff_key; +use function array_key_exists; use function array_map; use function array_merge; use function array_unique; @@ -310,6 +311,18 @@ private function validateParameters(array $parameters, array $parametersSchema): $context->path = ['parameters']; }; $processor->process($schema, $parameters); + + if ( + !array_key_exists('phpVersion', $parameters) + || !is_array($parameters['phpVersion'])) { + return; + } + + $phpVersion = $parameters['phpVersion']; + + if ($phpVersion['max'] < $phpVersion['min']) { + throw new InvalidPhpVersionException('Invalid PHP version range: phpVersion.max should be greater or equal to phpVersion.min.'); + } } /** diff --git a/src/DependencyInjection/InvalidPhpVersionException.php b/src/DependencyInjection/InvalidPhpVersionException.php new file mode 100644 index 0000000000..f9b41690a3 --- /dev/null +++ b/src/DependencyInjection/InvalidPhpVersionException.php @@ -0,0 +1,10 @@ +minVersion = new PhpVersion($phpVersion['min']); + $this->maxVersion = new PhpVersion($phpVersion['max']); + + return; + } + + if (!$bleedingEdge) { + return; + } + + // fallback to composer.json based php-version constraint + $composerPhpVersion = $this->getComposerRequireVersion(); + if ($composerPhpVersion === null) { + return; + } + + $parser = new VersionParser(); + $constraint = $parser->parseConstraints($composerPhpVersion); + + if (!$constraint->getLowerBound()->isZero()) { + $minVersion = $this->buildVersion($constraint->getLowerBound()->getVersion()); + + if ($minVersion !== null) { + $this->minVersion = new PhpVersion($minVersion->getVersionId()); + } + } + if ($constraint->getUpperBound()->isPositiveInfinity()) { + return; + } + + $this->maxVersion = $this->buildVersion($constraint->getUpperBound()->getVersion()); + } + + public function getMinVersion(): ?PhpVersion + { + return $this->minVersion; + } + + public function getMaxVersion(): ?PhpVersion + { + return $this->maxVersion; + } + + private function getComposerRequireVersion(): ?string + { + $composerPhpVersion = null; + if (count($this->composerAutoloaderProjectPaths) > 0) { + $composerJsonPath = end($this->composerAutoloaderProjectPaths) . '/composer.json'; + if (is_file($composerJsonPath)) { + try { + $composerJsonContents = FileReader::read($composerJsonPath); + $composer = Json::decode($composerJsonContents, Json::FORCE_ARRAY); + $requiredVersion = $composer['require']['php'] ?? null; + if (is_string($requiredVersion)) { + $composerPhpVersion = $requiredVersion; + } + } catch (CouldNotReadFileException | JsonException) { + // pass + } + } + } + return $composerPhpVersion; + } + + private function buildVersion(string $minVersion): ?PhpVersion + { + $matches = Strings::match($minVersion, '#^(\d+)\.(\d+)(?:\.(\d+))?#'); + if ($matches === null) { + return null; + } + + $major = $matches[1]; + $minor = $matches[2]; + $patch = $matches[3] ?? 0; + $versionId = (int) sprintf('%d%02d%02d', $major, $minor, $patch); + + return new PhpVersion($versionId); + } + +} diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index fcd6871c39..d23615eac7 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -41,11 +41,26 @@ public function getVersionId(): int return $this->versionId; } + public function getMajor(): int + { + return (int) floor($this->versionId / 10000); + } + + public function getMinor(): int + { + return (int) floor(($this->versionId % 10000) / 100); + } + + public function getPatch(): int + { + return (int) floor($this->versionId % 100); + } + public function getVersionString(): string { - $first = (int) floor($this->versionId / 10000); - $second = (int) floor(($this->versionId % 10000) / 100); - $third = (int) floor($this->versionId % 100); + $first = $this->getMajor(); + $second = $this->getMinor(); + $third = $this->getPatch(); return $first . '.' . $second . ($third !== 0 ? '.' . $third : ''); } diff --git a/src/Php/PhpVersionFactoryFactory.php b/src/Php/PhpVersionFactoryFactory.php index c578ef06fc..4eaee05bcb 100644 --- a/src/Php/PhpVersionFactoryFactory.php +++ b/src/Php/PhpVersionFactoryFactory.php @@ -8,18 +8,22 @@ use PHPStan\File\FileReader; use function count; use function end; +use function is_array; use function is_file; +use function is_int; use function is_string; final class PhpVersionFactoryFactory { /** + * @param int|array{min: int, max: int}|null $phpVersion * @param string[] $composerAutoloaderProjectPaths */ public function __construct( - private ?int $versionId, + private int|array|null $phpVersion, private array $composerAutoloaderProjectPaths, + private bool $bleedingEdge, ) { } @@ -43,7 +47,17 @@ public function create(): PhpVersionFactory } } - return new PhpVersionFactory($this->versionId, $composerPhpVersion); + $versionId = null; + + if (is_int($this->phpVersion)) { + $versionId = $this->phpVersion; + } + + if ($this->bleedingEdge && is_array($this->phpVersion)) { + $versionId = $this->phpVersion['min']; + } + + return new PhpVersionFactory($versionId, $composerPhpVersion); } } diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 87d5bab299..aee824bfbc 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -137,7 +137,7 @@ public static function createScopeFactory(ReflectionProvider $reflectionProvider } $reflectionProviderProvider = new DirectReflectionProviderProvider($reflectionProvider); - $constantResolver = new ConstantResolver($reflectionProviderProvider, $dynamicConstantNames); + $constantResolver = new ConstantResolver($reflectionProviderProvider, $dynamicConstantNames, null, null); $initializerExprTypeResolver = new InitializerExprTypeResolver( $constantResolver, diff --git a/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php b/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php index 9a5592bf40..d79ebf0706 100644 --- a/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php @@ -2,12 +2,15 @@ namespace PHPStan\Type\Php; +use PhpParser\Node\Expr; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\Php\ComposerPhpVersionFactory; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -18,6 +21,10 @@ final class VersionCompareFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { + public function __construct(private ComposerPhpVersionFactory $composerPhpVersionFactory) + { + } + public function isFunctionSupported(FunctionReflection $functionReflection): bool { return $functionReflection->getName() === 'version_compare'; @@ -29,19 +36,20 @@ public function getTypeFromFunctionCall( Scope $scope, ): ?Type { - if (count($functionCall->getArgs()) < 2) { + $args = $functionCall->getArgs(); + if (count($args) < 2) { return null; } - $version1Strings = $scope->getType($functionCall->getArgs()[0]->value)->getConstantStrings(); - $version2Strings = $scope->getType($functionCall->getArgs()[1]->value)->getConstantStrings(); + $version1Strings = $this->getVersionStrings($args[0]->value, $scope); + $version2Strings = $this->getVersionStrings($args[1]->value, $scope); $counts = [ count($version1Strings), count($version2Strings), ]; - if (isset($functionCall->getArgs()[2])) { - $operatorStrings = $scope->getType($functionCall->getArgs()[2]->value)->getConstantStrings(); + if (isset($args[2])) { + $operatorStrings = $scope->getType($args[2]->value)->getConstantStrings(); $counts[] = count($operatorStrings); $returnType = new BooleanType(); } else { @@ -77,4 +85,24 @@ public function getTypeFromFunctionCall( return TypeCombinator::union(...$types); } + /** + * @return ConstantStringType[] + */ + private function getVersionStrings(Expr $expr, Scope $scope): array + { + if ( + $expr instanceof Expr\ConstFetch + && $expr->name->toString() === 'PHP_VERSION' + && $this->composerPhpVersionFactory->getMinVersion() !== null + && $this->composerPhpVersionFactory->getMaxVersion() !== null + ) { + return [ + new ConstantStringType($this->composerPhpVersionFactory->getMinVersion()->getVersionString()), + new ConstantStringType($this->composerPhpVersionFactory->getMaxVersion()->getVersionString()), + ]; + } + + return $scope->getType($expr)->getConstantStrings(); + } + } From d97463d258fc166a4bb4675cbd6d2befbc7421fe Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 26 Oct 2024 11:37:44 +0200 Subject: [PATCH 02/21] use bashunit --- .github/workflows/e2e-tests.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 517086cb68..3f46100131 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -318,9 +318,10 @@ jobs: ../../bin/phpstan analyze test.php --level=0 - script: | cd e2e/composer-version-config-invalid - OUTPUT=$(../../bin/phpstan 2>&1 || true) - grep 'Invalid configuration' <<< "$OUTPUT" - grep 'Invalid PHP version range: phpVersion.max should be greater or equal to phpVersion.min.' <<< "$OUTPUT" + OUTPUT=$(../bashunit -a exit_code "1" ../../bin/phpstan) + echo "$OUTPUT" + ../bashunit -a contains 'Invalid configuration' "$OUTPUT" + ../bashunit -a contains 'Invalid PHP version range: phpVersion.max should be greater or equal to phpVersion.min.' "$OUTPUT" steps: - name: "Checkout" @@ -337,5 +338,8 @@ jobs: - name: "Install dependencies" run: "composer install --no-interaction --no-progress" + - name: "Install bashunit" + run: "curl -s https://bashunit.typeddevs.com/install.sh | bash -s e2e/ 0.17.0" + - name: "Test" run: ${{ matrix.script }} From 13cd975f0d473f6adf50ace933ee8f917066c5d6 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 26 Oct 2024 12:20:14 +0200 Subject: [PATCH 03/21] use separate feature toggle --- conf/bleedingEdge.neon | 2 ++ conf/config.neon | 5 +++-- conf/parametersSchema.neon | 1 + src/Php/ComposerPhpVersionFactory.php | 4 ++-- src/Php/PhpVersionFactoryFactory.php | 4 ++-- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 8e06b22fda..0385f2dd80 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -3,3 +3,5 @@ parameters: bleedingEdge: true skipCheckGenericClasses!: [] stricterFunctionMap: true + narrowPhpVersionFromComposerJson: true + diff --git a/conf/config.neon b/conf/config.neon index 676acf01ab..3aa16d7aad 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -24,6 +24,7 @@ parameters: bleedingEdge: false skipCheckGenericClasses: [] stricterFunctionMap: false + narrowPhpVersionFromComposerJson: false fileExtensions: - php checkAdvancedIsset: false @@ -341,14 +342,14 @@ services: arguments: phpVersion: %phpVersion% composerAutoloaderProjectPaths: %composerAutoloaderProjectPaths% - bleedingEdge: %featureToggles.bleedingEdge% + narrowPhpVersionFromComposerJson: %featureToggles.narrowPhpVersionFromComposerJson% - class: PHPStan\Php\ComposerPhpVersionFactory arguments: phpVersion: %phpVersion% composerAutoloaderProjectPaths: %composerAutoloaderProjectPaths% - bleedingEdge: %featureToggles.bleedingEdge% + narrowPhpVersionFromComposerJson: %featureToggles.narrowPhpVersionFromComposerJson% - class: PHPStan\PhpDocParser\ParserConfig diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 6c7f9c40e6..0553578c07 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -30,6 +30,7 @@ parametersSchema: bleedingEdge: bool(), skipCheckGenericClasses: listOf(string()), stricterFunctionMap: bool() + narrowPhpVersionFromComposerJson: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() diff --git a/src/Php/ComposerPhpVersionFactory.php b/src/Php/ComposerPhpVersionFactory.php index c8c945afca..01059f601a 100644 --- a/src/Php/ComposerPhpVersionFactory.php +++ b/src/Php/ComposerPhpVersionFactory.php @@ -31,7 +31,7 @@ final class ComposerPhpVersionFactory public function __construct( private array $composerAutoloaderProjectPaths, int|array|null $phpVersion, - bool $bleedingEdge, + bool $narrowPhpVersionFromComposerJson, ) { if (is_int($phpVersion)) { @@ -49,7 +49,7 @@ public function __construct( return; } - if (!$bleedingEdge) { + if (!$narrowPhpVersionFromComposerJson) { return; } diff --git a/src/Php/PhpVersionFactoryFactory.php b/src/Php/PhpVersionFactoryFactory.php index 4eaee05bcb..d10f87411f 100644 --- a/src/Php/PhpVersionFactoryFactory.php +++ b/src/Php/PhpVersionFactoryFactory.php @@ -23,7 +23,7 @@ final class PhpVersionFactoryFactory public function __construct( private int|array|null $phpVersion, private array $composerAutoloaderProjectPaths, - private bool $bleedingEdge, + private bool $narrowPhpVersionFromComposerJson, ) { } @@ -53,7 +53,7 @@ public function create(): PhpVersionFactory $versionId = $this->phpVersion; } - if ($this->bleedingEdge && is_array($this->phpVersion)) { + if ($this->narrowPhpVersionFromComposerJson && is_array($this->phpVersion)) { $versionId = $this->phpVersion['min']; } From 1f855824959bec6de3dad26734ec9a6c57c27b4d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 5 Nov 2024 16:26:52 +0100 Subject: [PATCH 04/21] removed feature toggle --- conf/bleedingEdge.neon | 1 - conf/config.neon | 3 --- conf/parametersSchema.neon | 1 - src/Php/ComposerPhpVersionFactory.php | 5 ----- src/Php/PhpVersionFactoryFactory.php | 3 +-- 5 files changed, 1 insertion(+), 12 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 0385f2dd80..1a5863a4ad 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -3,5 +3,4 @@ parameters: bleedingEdge: true skipCheckGenericClasses!: [] stricterFunctionMap: true - narrowPhpVersionFromComposerJson: true diff --git a/conf/config.neon b/conf/config.neon index 3aa16d7aad..df5c15ddec 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -24,7 +24,6 @@ parameters: bleedingEdge: false skipCheckGenericClasses: [] stricterFunctionMap: false - narrowPhpVersionFromComposerJson: false fileExtensions: - php checkAdvancedIsset: false @@ -342,14 +341,12 @@ services: arguments: phpVersion: %phpVersion% composerAutoloaderProjectPaths: %composerAutoloaderProjectPaths% - narrowPhpVersionFromComposerJson: %featureToggles.narrowPhpVersionFromComposerJson% - class: PHPStan\Php\ComposerPhpVersionFactory arguments: phpVersion: %phpVersion% composerAutoloaderProjectPaths: %composerAutoloaderProjectPaths% - narrowPhpVersionFromComposerJson: %featureToggles.narrowPhpVersionFromComposerJson% - class: PHPStan\PhpDocParser\ParserConfig diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 0553578c07..6c7f9c40e6 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -30,7 +30,6 @@ parametersSchema: bleedingEdge: bool(), skipCheckGenericClasses: listOf(string()), stricterFunctionMap: bool() - narrowPhpVersionFromComposerJson: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() diff --git a/src/Php/ComposerPhpVersionFactory.php b/src/Php/ComposerPhpVersionFactory.php index 01059f601a..8c2538c48c 100644 --- a/src/Php/ComposerPhpVersionFactory.php +++ b/src/Php/ComposerPhpVersionFactory.php @@ -31,7 +31,6 @@ final class ComposerPhpVersionFactory public function __construct( private array $composerAutoloaderProjectPaths, int|array|null $phpVersion, - bool $narrowPhpVersionFromComposerJson, ) { if (is_int($phpVersion)) { @@ -49,10 +48,6 @@ public function __construct( return; } - if (!$narrowPhpVersionFromComposerJson) { - return; - } - // fallback to composer.json based php-version constraint $composerPhpVersion = $this->getComposerRequireVersion(); if ($composerPhpVersion === null) { diff --git a/src/Php/PhpVersionFactoryFactory.php b/src/Php/PhpVersionFactoryFactory.php index d10f87411f..0190ca0e82 100644 --- a/src/Php/PhpVersionFactoryFactory.php +++ b/src/Php/PhpVersionFactoryFactory.php @@ -23,7 +23,6 @@ final class PhpVersionFactoryFactory public function __construct( private int|array|null $phpVersion, private array $composerAutoloaderProjectPaths, - private bool $narrowPhpVersionFromComposerJson, ) { } @@ -53,7 +52,7 @@ public function create(): PhpVersionFactory $versionId = $this->phpVersion; } - if ($this->narrowPhpVersionFromComposerJson && is_array($this->phpVersion)) { + if (is_array($this->phpVersion)) { $versionId = $this->phpVersion['min']; } From b31f27522b8255d4166467e5be1cb4d56ed501bf Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 5 Nov 2024 16:28:45 +0100 Subject: [PATCH 05/21] Discard changes to conf/bleedingEdge.neon --- conf/bleedingEdge.neon | 1 - 1 file changed, 1 deletion(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 1a5863a4ad..8e06b22fda 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -3,4 +3,3 @@ parameters: bleedingEdge: true skipCheckGenericClasses!: [] stricterFunctionMap: true - From 1c490916f5591cf1658715ffa110d3b0d7181981 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 5 Nov 2024 16:35:12 +0100 Subject: [PATCH 06/21] remove outdated test --- .github/workflows/e2e-tests.yml | 4 ---- e2e/composer-min-version-bc/.gitignore | 2 -- e2e/composer-min-version-bc/composer.json | 5 ----- e2e/composer-min-version-bc/test.php | 7 ------- 4 files changed, 18 deletions(-) delete mode 100644 e2e/composer-min-version-bc/.gitignore delete mode 100644 e2e/composer-min-version-bc/composer.json delete mode 100644 e2e/composer-min-version-bc/test.php diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 3f46100131..3f4694354e 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -304,10 +304,6 @@ jobs: cd e2e/composer-min-open-end-version composer install ../../bin/phpstan analyze test.php --level=0 - - script: | - cd e2e/composer-min-version-bc - composer install - ../../bin/phpstan analyze test.php --level=0 - script: | cd e2e/composer-min-version composer install diff --git a/e2e/composer-min-version-bc/.gitignore b/e2e/composer-min-version-bc/.gitignore deleted file mode 100644 index 3a9875b460..0000000000 --- a/e2e/composer-min-version-bc/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/vendor/ -composer.lock diff --git a/e2e/composer-min-version-bc/composer.json b/e2e/composer-min-version-bc/composer.json deleted file mode 100644 index 9be64619f1..0000000000 --- a/e2e/composer-min-version-bc/composer.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "require": { - "php": "^8.1" - } -} diff --git a/e2e/composer-min-version-bc/test.php b/e2e/composer-min-version-bc/test.php deleted file mode 100644 index 7a04552626..0000000000 --- a/e2e/composer-min-version-bc/test.php +++ /dev/null @@ -1,7 +0,0 @@ -', PHP_VERSION_ID); -\PHPStan\Testing\assertType('int<5, max>', PHP_MAJOR_VERSION); -\PHPStan\Testing\assertType('int<0, max>', PHP_MINOR_VERSION); -\PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); From 181777ffbf615e084ce99975057fadbccd985281 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 6 Nov 2024 11:53:47 +0100 Subject: [PATCH 07/21] rename methods --- src/Analyser/ConstantResolver.php | 18 +++++++++--------- src/Php/PhpVersion.php | 12 ++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Analyser/ConstantResolver.php b/src/Analyser/ConstantResolver.php index 6b73db97e4..8176fe8abc 100644 --- a/src/Analyser/ConstantResolver.php +++ b/src/Analyser/ConstantResolver.php @@ -88,10 +88,10 @@ public function resolvePredefinedConstant(string $resolvedConstantName): ?Type $maxMajor = null; if ($this->composerMinPhpVersion !== null) { - $minMajor = max($minMajor, $this->composerMinPhpVersion->getMajor()); + $minMajor = max($minMajor, $this->composerMinPhpVersion->getMajorVersionId()); } if ($this->composerMaxPhpVersion !== null) { - $maxMajor = $this->composerMaxPhpVersion->getMajor(); + $maxMajor = $this->composerMaxPhpVersion->getMajorVersionId(); } return $this->createInteger($minMajor, $maxMajor); @@ -103,10 +103,10 @@ public function resolvePredefinedConstant(string $resolvedConstantName): ?Type if ( $this->composerMinPhpVersion !== null && $this->composerMaxPhpVersion !== null - && $this->composerMaxPhpVersion->getMajor() === $this->composerMinPhpVersion->getMajor() + && $this->composerMaxPhpVersion->getMajorVersionId() === $this->composerMinPhpVersion->getMajorVersionId() ) { - $minMinor = $this->composerMinPhpVersion->getMinor(); - $maxMinor = $this->composerMaxPhpVersion->getMinor(); + $minMinor = $this->composerMinPhpVersion->getMinorVersionId(); + $maxMinor = $this->composerMaxPhpVersion->getMinorVersionId(); } return $this->createInteger($minMinor, $maxMinor); @@ -118,11 +118,11 @@ public function resolvePredefinedConstant(string $resolvedConstantName): ?Type if ( $this->composerMinPhpVersion !== null && $this->composerMaxPhpVersion !== null - && $this->composerMaxPhpVersion->getMajor() === $this->composerMinPhpVersion->getMajor() - && $this->composerMaxPhpVersion->getMinor() === $this->composerMinPhpVersion->getMinor() + && $this->composerMaxPhpVersion->getMajorVersionId() === $this->composerMinPhpVersion->getMajorVersionId() + && $this->composerMaxPhpVersion->getMinorVersionId() === $this->composerMinPhpVersion->getMinorVersionId() ) { - $minRelease = $this->composerMinPhpVersion->getPatch(); - $maxRelease = $this->composerMaxPhpVersion->getPatch(); + $minRelease = $this->composerMinPhpVersion->getPatchVersionId(); + $maxRelease = $this->composerMaxPhpVersion->getPatchVersionId(); } return $this->createInteger($minRelease, $maxRelease); diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index d23615eac7..83ca1245b5 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -41,26 +41,26 @@ public function getVersionId(): int return $this->versionId; } - public function getMajor(): int + public function getMajorVersionId(): int { return (int) floor($this->versionId / 10000); } - public function getMinor(): int + public function getMinorVersionId(): int { return (int) floor(($this->versionId % 10000) / 100); } - public function getPatch(): int + public function getPatchVersionId(): int { return (int) floor($this->versionId % 100); } public function getVersionString(): string { - $first = $this->getMajor(); - $second = $this->getMinor(); - $third = $this->getPatch(); + $first = $this->getMajorVersionId(); + $second = $this->getMinorVersionId(); + $third = $this->getPatchVersionId(); return $first . '.' . $second . ($third !== 0 ? '.' . $third : ''); } From 554132ae612e952445a7ecb5d577e2b4496dab7b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 6 Nov 2024 12:01:56 +0100 Subject: [PATCH 08/21] lazy init min/max version --- src/Php/ComposerPhpVersionFactory.php | 30 +++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/Php/ComposerPhpVersionFactory.php b/src/Php/ComposerPhpVersionFactory.php index 8c2538c48c..4201d71edd 100644 --- a/src/Php/ComposerPhpVersionFactory.php +++ b/src/Php/ComposerPhpVersionFactory.php @@ -24,17 +24,27 @@ final class ComposerPhpVersionFactory private ?PhpVersion $maxVersion = null; + private bool $initialized = false; + /** * @param string[] $composerAutoloaderProjectPaths * @param int|array{min: int, max: int}|null $phpVersion */ public function __construct( private array $composerAutoloaderProjectPaths, - int|array|null $phpVersion, + private int|array|null $phpVersion, ) { + } + + private function initVersions(): void + { + $this->initialized = true; + + $phpVersion = $this->phpVersion; + if (is_int($phpVersion)) { - return; + throw new ShouldNotHappenException(); } if (is_array($phpVersion)) { @@ -73,11 +83,27 @@ public function __construct( public function getMinVersion(): ?PhpVersion { + if (is_int($this->phpVersion)) { + return null; + } + + if ($this->initialized === false) { + $this->initVersions(); + } + return $this->minVersion; } public function getMaxVersion(): ?PhpVersion { + if (is_int($this->phpVersion)) { + return null; + } + + if ($this->initialized === false) { + $this->initVersions(); + } + return $this->maxVersion; } From 0d7838802c552a7bff65402574809778f063e936 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 6 Nov 2024 12:16:01 +0100 Subject: [PATCH 09/21] enforce supported version bounds --- e2e/composer-min-open-end-version/test.php | 6 +++--- e2e/composer-min-version/test.php | 6 +++--- src/Php/ComposerPhpVersionFactory.php | 8 ++++++++ src/Php/PhpVersionFactory.php | 7 +++++-- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/e2e/composer-min-open-end-version/test.php b/e2e/composer-min-open-end-version/test.php index d35bd6bca0..d4d06a34eb 100644 --- a/e2e/composer-min-open-end-version/test.php +++ b/e2e/composer-min-open-end-version/test.php @@ -1,6 +1,6 @@ ', PHP_VERSION_ID); -\PHPStan\Testing\assertType('int<8, max>', PHP_MAJOR_VERSION); -\PHPStan\Testing\assertType('int<0, max>', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<80100, 80499>', PHP_VERSION_ID); +\PHPStan\Testing\assertType('8', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<1, 4>', PHP_MINOR_VERSION); \PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); diff --git a/e2e/composer-min-version/test.php b/e2e/composer-min-version/test.php index a88c78b607..d4d06a34eb 100644 --- a/e2e/composer-min-version/test.php +++ b/e2e/composer-min-version/test.php @@ -1,6 +1,6 @@ ', PHP_VERSION_ID); -\PHPStan\Testing\assertType('int<8, 9>', PHP_MAJOR_VERSION); -\PHPStan\Testing\assertType('int<0, max>', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<80100, 80499>', PHP_VERSION_ID); +\PHPStan\Testing\assertType('8', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<1, 4>', PHP_MINOR_VERSION); \PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); diff --git a/src/Php/ComposerPhpVersionFactory.php b/src/Php/ComposerPhpVersionFactory.php index 4201d71edd..709fe7eeec 100644 --- a/src/Php/ComposerPhpVersionFactory.php +++ b/src/Php/ComposerPhpVersionFactory.php @@ -15,6 +15,8 @@ use function is_file; use function is_int; use function is_string; +use function max; +use function min; use function sprintf; final class ComposerPhpVersionFactory @@ -58,6 +60,9 @@ private function initVersions(): void return; } + $this->minVersion = new PhpVersion(PhpVersionFactory::MIN_PHP_VERSION); + $this->maxVersion = new PhpVersion(PhpVersionFactory::MAX_PHP_VERSION); + // fallback to composer.json based php-version constraint $composerPhpVersion = $this->getComposerRequireVersion(); if ($composerPhpVersion === null) { @@ -140,6 +145,9 @@ private function buildVersion(string $minVersion): ?PhpVersion $patch = $matches[3] ?? 0; $versionId = (int) sprintf('%d%02d%02d', $major, $minor, $patch); + $versionId = max($versionId, PhpVersionFactory::MIN_PHP_VERSION); + $versionId = min($versionId, PhpVersionFactory::MAX_PHP_VERSION); + return new PhpVersion($versionId); } diff --git a/src/Php/PhpVersionFactory.php b/src/Php/PhpVersionFactory.php index d926420e77..2f4b566481 100644 --- a/src/Php/PhpVersionFactory.php +++ b/src/Php/PhpVersionFactory.php @@ -10,6 +10,9 @@ final class PhpVersionFactory { + public const MIN_PHP_VERSION = 70100; + public const MAX_PHP_VERSION = 80499; + public function __construct( private ?int $versionId, private ?string $composerPhpVersion, @@ -25,8 +28,8 @@ public function create(): PhpVersion } elseif ($this->composerPhpVersion !== null) { $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, 80499); + $tmp = max($tmp, self::MIN_PHP_VERSION); + $versionId = min($tmp, self::MAX_PHP_VERSION); $source = PhpVersion::SOURCE_COMPOSER_PLATFORM_PHP; } else { $versionId = PHP_VERSION_ID; From dfb06cc54fb8c1867ccc523fff5b9e443f8361a3 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 6 Nov 2024 12:22:05 +0100 Subject: [PATCH 10/21] naming --- src/Php/ComposerPhpVersionFactory.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Php/ComposerPhpVersionFactory.php b/src/Php/ComposerPhpVersionFactory.php index 709fe7eeec..a792a1cf6b 100644 --- a/src/Php/ComposerPhpVersionFactory.php +++ b/src/Php/ComposerPhpVersionFactory.php @@ -39,7 +39,7 @@ public function __construct( { } - private function initVersions(): void + private function initializeVersions(): void { $this->initialized = true; @@ -93,7 +93,7 @@ public function getMinVersion(): ?PhpVersion } if ($this->initialized === false) { - $this->initVersions(); + $this->initializeVersions(); } return $this->minVersion; @@ -106,7 +106,7 @@ public function getMaxVersion(): ?PhpVersion } if ($this->initialized === false) { - $this->initVersions(); + $this->initializeVersions(); } return $this->maxVersion; From fe0ae98c3b6866bd30fad563d1f91229f5fe9170 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 6 Nov 2024 12:31:02 +0100 Subject: [PATCH 11/21] Assert new error --- tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 37a92688b7..8b5c0631c0 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -457,7 +457,9 @@ public function testBug5527(): void public function testBug5639(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-5639.php'); - $this->assertNoErrors($errors); + $this->assertCount(1, $errors); + $this->assertSame('If condition is always false.', $errors[0]->getMessage()); + $this->assertSame(5, $errors[0]->getLine()); } public function testBug5657(): void From 1753b9f5bc4f77ed5046276174c9246be02f727d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 6 Nov 2024 12:31:56 +0100 Subject: [PATCH 12/21] Update test.php --- e2e/composer-max-version/test.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/e2e/composer-max-version/test.php b/e2e/composer-max-version/test.php index 038f559122..c50ee1c69a 100644 --- a/e2e/composer-max-version/test.php +++ b/e2e/composer-max-version/test.php @@ -1,10 +1,10 @@ ', PHP_VERSION_ID); -\PHPStan\Testing\assertType('int<5, 8>', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<70100, 80300>', PHP_VERSION_ID); +\PHPStan\Testing\assertType('int<7, 8>', PHP_MAJOR_VERSION); \PHPStan\Testing\assertType('int<0, max>', PHP_MINOR_VERSION); \PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); -\PHPStan\Testing\assertType('-1|0|1', version_compare(PHP_VERSION, '7.0.0')); -\PHPStan\Testing\assertType('bool', version_compare(PHP_VERSION, '7.0.0', '<')); -\PHPStan\Testing\assertType('bool', version_compare(PHP_VERSION, '7.0.0', '>')); +\PHPStan\Testing\assertType('1', version_compare(PHP_VERSION, '7.0.0')); +\PHPStan\Testing\assertType('false', version_compare(PHP_VERSION, '7.0.0', '<')); +\PHPStan\Testing\assertType('true', version_compare(PHP_VERSION, '7.0.0', '>')); From 034a4d64c6d233b3acaaac6a9fb079e27ba32aab Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 6 Nov 2024 15:14:39 +0100 Subject: [PATCH 13/21] added tests --- e2e/composer-min-version-v5/.gitignore | 2 ++ e2e/composer-min-version-v5/composer.json | 5 +++++ e2e/composer-min-version-v5/phpstan.neon | 2 ++ e2e/composer-min-version-v5/test.php | 6 ++++++ e2e/composer-min-version-v7/.gitignore | 2 ++ e2e/composer-min-version-v7/composer.json | 5 +++++ e2e/composer-min-version-v7/phpstan.neon | 2 ++ e2e/composer-min-version-v7/test.php | 6 ++++++ e2e/composer-no-versions/.gitignore | 2 ++ e2e/composer-no-versions/composer.json | 2 ++ e2e/composer-no-versions/phpstan.neon | 2 ++ e2e/composer-no-versions/test.php | 6 ++++++ src/Php/ComposerPhpVersionFactory.php | 3 +-- 13 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 e2e/composer-min-version-v5/.gitignore create mode 100644 e2e/composer-min-version-v5/composer.json create mode 100644 e2e/composer-min-version-v5/phpstan.neon create mode 100644 e2e/composer-min-version-v5/test.php create mode 100644 e2e/composer-min-version-v7/.gitignore create mode 100644 e2e/composer-min-version-v7/composer.json create mode 100644 e2e/composer-min-version-v7/phpstan.neon create mode 100644 e2e/composer-min-version-v7/test.php create mode 100644 e2e/composer-no-versions/.gitignore create mode 100644 e2e/composer-no-versions/composer.json create mode 100644 e2e/composer-no-versions/phpstan.neon create mode 100644 e2e/composer-no-versions/test.php diff --git a/e2e/composer-min-version-v5/.gitignore b/e2e/composer-min-version-v5/.gitignore new file mode 100644 index 0000000000..3a9875b460 --- /dev/null +++ b/e2e/composer-min-version-v5/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-min-version-v5/composer.json b/e2e/composer-min-version-v5/composer.json new file mode 100644 index 0000000000..b73464d219 --- /dev/null +++ b/e2e/composer-min-version-v5/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": "^5.6" + } +} diff --git a/e2e/composer-min-version-v5/phpstan.neon b/e2e/composer-min-version-v5/phpstan.neon new file mode 100644 index 0000000000..49d50a5bac --- /dev/null +++ b/e2e/composer-min-version-v5/phpstan.neon @@ -0,0 +1,2 @@ +includes: + - ../../conf/bleedingEdge.neon diff --git a/e2e/composer-min-version-v5/test.php b/e2e/composer-min-version-v5/test.php new file mode 100644 index 0000000000..5a90b6c6ba --- /dev/null +++ b/e2e/composer-min-version-v5/test.php @@ -0,0 +1,6 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('5', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('6', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); diff --git a/e2e/composer-min-version-v7/.gitignore b/e2e/composer-min-version-v7/.gitignore new file mode 100644 index 0000000000..3a9875b460 --- /dev/null +++ b/e2e/composer-min-version-v7/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-min-version-v7/composer.json b/e2e/composer-min-version-v7/composer.json new file mode 100644 index 0000000000..9f9b263871 --- /dev/null +++ b/e2e/composer-min-version-v7/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": "^7" + } +} diff --git a/e2e/composer-min-version-v7/phpstan.neon b/e2e/composer-min-version-v7/phpstan.neon new file mode 100644 index 0000000000..49d50a5bac --- /dev/null +++ b/e2e/composer-min-version-v7/phpstan.neon @@ -0,0 +1,2 @@ +includes: + - ../../conf/bleedingEdge.neon diff --git a/e2e/composer-min-version-v7/test.php b/e2e/composer-min-version-v7/test.php new file mode 100644 index 0000000000..e8876bd78f --- /dev/null +++ b/e2e/composer-min-version-v7/test.php @@ -0,0 +1,6 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('7', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<0, 4>', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); diff --git a/e2e/composer-no-versions/.gitignore b/e2e/composer-no-versions/.gitignore new file mode 100644 index 0000000000..3a9875b460 --- /dev/null +++ b/e2e/composer-no-versions/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-no-versions/composer.json b/e2e/composer-no-versions/composer.json new file mode 100644 index 0000000000..2c63c08510 --- /dev/null +++ b/e2e/composer-no-versions/composer.json @@ -0,0 +1,2 @@ +{ +} diff --git a/e2e/composer-no-versions/phpstan.neon b/e2e/composer-no-versions/phpstan.neon new file mode 100644 index 0000000000..49d50a5bac --- /dev/null +++ b/e2e/composer-no-versions/phpstan.neon @@ -0,0 +1,2 @@ +includes: + - ../../conf/bleedingEdge.neon diff --git a/e2e/composer-no-versions/test.php b/e2e/composer-no-versions/test.php new file mode 100644 index 0000000000..28c8a3183b --- /dev/null +++ b/e2e/composer-no-versions/test.php @@ -0,0 +1,6 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('int<5, 8>', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); diff --git a/src/Php/ComposerPhpVersionFactory.php b/src/Php/ComposerPhpVersionFactory.php index a792a1cf6b..23ce3fe54d 100644 --- a/src/Php/ComposerPhpVersionFactory.php +++ b/src/Php/ComposerPhpVersionFactory.php @@ -60,7 +60,7 @@ private function initializeVersions(): void return; } - $this->minVersion = new PhpVersion(PhpVersionFactory::MIN_PHP_VERSION); + // don't limit minVersion... PHPStan can analyze even PHP5 $this->maxVersion = new PhpVersion(PhpVersionFactory::MAX_PHP_VERSION); // fallback to composer.json based php-version constraint @@ -145,7 +145,6 @@ private function buildVersion(string $minVersion): ?PhpVersion $patch = $matches[3] ?? 0; $versionId = (int) sprintf('%d%02d%02d', $major, $minor, $patch); - $versionId = max($versionId, PhpVersionFactory::MIN_PHP_VERSION); $versionId = min($versionId, PhpVersionFactory::MAX_PHP_VERSION); return new PhpVersion($versionId); From a3522b7c00c29462bf27682d9cc0263d0d244bd5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 6 Nov 2024 15:17:26 +0100 Subject: [PATCH 14/21] cs --- e2e/composer-max-version/test.php | 10 +++++----- src/Php/ComposerPhpVersionFactory.php | 1 - tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 4 +--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/e2e/composer-max-version/test.php b/e2e/composer-max-version/test.php index c50ee1c69a..038f559122 100644 --- a/e2e/composer-max-version/test.php +++ b/e2e/composer-max-version/test.php @@ -1,10 +1,10 @@ ', PHP_VERSION_ID); -\PHPStan\Testing\assertType('int<7, 8>', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<50207, 80300>', PHP_VERSION_ID); +\PHPStan\Testing\assertType('int<5, 8>', PHP_MAJOR_VERSION); \PHPStan\Testing\assertType('int<0, max>', PHP_MINOR_VERSION); \PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); -\PHPStan\Testing\assertType('1', version_compare(PHP_VERSION, '7.0.0')); -\PHPStan\Testing\assertType('false', version_compare(PHP_VERSION, '7.0.0', '<')); -\PHPStan\Testing\assertType('true', version_compare(PHP_VERSION, '7.0.0', '>')); +\PHPStan\Testing\assertType('-1|0|1', version_compare(PHP_VERSION, '7.0.0')); +\PHPStan\Testing\assertType('bool', version_compare(PHP_VERSION, '7.0.0', '<')); +\PHPStan\Testing\assertType('bool', version_compare(PHP_VERSION, '7.0.0', '>')); diff --git a/src/Php/ComposerPhpVersionFactory.php b/src/Php/ComposerPhpVersionFactory.php index 23ce3fe54d..20fd3a52ad 100644 --- a/src/Php/ComposerPhpVersionFactory.php +++ b/src/Php/ComposerPhpVersionFactory.php @@ -15,7 +15,6 @@ use function is_file; use function is_int; use function is_string; -use function max; use function min; use function sprintf; diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 8b5c0631c0..37a92688b7 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -457,9 +457,7 @@ public function testBug5527(): void public function testBug5639(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-5639.php'); - $this->assertCount(1, $errors); - $this->assertSame('If condition is always false.', $errors[0]->getMessage()); - $this->assertSame(5, $errors[0]->getLine()); + $this->assertNoErrors($errors); } public function testBug5657(): void From e7a159274b6db14ff3fb3efb951a94b94328142e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 6 Nov 2024 15:25:31 +0100 Subject: [PATCH 15/21] drop unnecessary use of bleeding edge --- e2e/composer-max-version/phpstan.neon | 2 -- e2e/composer-min-max-version/phpstan.neon | 2 -- e2e/composer-min-open-end-version/phpstan.neon | 2 -- e2e/composer-min-version-v5/phpstan.neon | 2 -- e2e/composer-min-version-v7/phpstan.neon | 2 -- e2e/composer-min-version/phpstan.neon | 2 -- e2e/composer-no-versions/phpstan.neon | 2 -- e2e/composer-version-config-invalid/phpstan.neon | 2 -- e2e/composer-version-config-patch/phpstan.neon | 2 -- e2e/composer-version-config/phpstan.neon | 2 -- 10 files changed, 20 deletions(-) delete mode 100644 e2e/composer-max-version/phpstan.neon delete mode 100644 e2e/composer-min-max-version/phpstan.neon delete mode 100644 e2e/composer-min-open-end-version/phpstan.neon delete mode 100644 e2e/composer-min-version-v5/phpstan.neon delete mode 100644 e2e/composer-min-version-v7/phpstan.neon delete mode 100644 e2e/composer-min-version/phpstan.neon delete mode 100644 e2e/composer-no-versions/phpstan.neon delete mode 100644 e2e/composer-version-config-patch/phpstan.neon diff --git a/e2e/composer-max-version/phpstan.neon b/e2e/composer-max-version/phpstan.neon deleted file mode 100644 index 49d50a5bac..0000000000 --- a/e2e/composer-max-version/phpstan.neon +++ /dev/null @@ -1,2 +0,0 @@ -includes: - - ../../conf/bleedingEdge.neon diff --git a/e2e/composer-min-max-version/phpstan.neon b/e2e/composer-min-max-version/phpstan.neon deleted file mode 100644 index 49d50a5bac..0000000000 --- a/e2e/composer-min-max-version/phpstan.neon +++ /dev/null @@ -1,2 +0,0 @@ -includes: - - ../../conf/bleedingEdge.neon diff --git a/e2e/composer-min-open-end-version/phpstan.neon b/e2e/composer-min-open-end-version/phpstan.neon deleted file mode 100644 index 49d50a5bac..0000000000 --- a/e2e/composer-min-open-end-version/phpstan.neon +++ /dev/null @@ -1,2 +0,0 @@ -includes: - - ../../conf/bleedingEdge.neon diff --git a/e2e/composer-min-version-v5/phpstan.neon b/e2e/composer-min-version-v5/phpstan.neon deleted file mode 100644 index 49d50a5bac..0000000000 --- a/e2e/composer-min-version-v5/phpstan.neon +++ /dev/null @@ -1,2 +0,0 @@ -includes: - - ../../conf/bleedingEdge.neon diff --git a/e2e/composer-min-version-v7/phpstan.neon b/e2e/composer-min-version-v7/phpstan.neon deleted file mode 100644 index 49d50a5bac..0000000000 --- a/e2e/composer-min-version-v7/phpstan.neon +++ /dev/null @@ -1,2 +0,0 @@ -includes: - - ../../conf/bleedingEdge.neon diff --git a/e2e/composer-min-version/phpstan.neon b/e2e/composer-min-version/phpstan.neon deleted file mode 100644 index 49d50a5bac..0000000000 --- a/e2e/composer-min-version/phpstan.neon +++ /dev/null @@ -1,2 +0,0 @@ -includes: - - ../../conf/bleedingEdge.neon diff --git a/e2e/composer-no-versions/phpstan.neon b/e2e/composer-no-versions/phpstan.neon deleted file mode 100644 index 49d50a5bac..0000000000 --- a/e2e/composer-no-versions/phpstan.neon +++ /dev/null @@ -1,2 +0,0 @@ -includes: - - ../../conf/bleedingEdge.neon diff --git a/e2e/composer-version-config-invalid/phpstan.neon b/e2e/composer-version-config-invalid/phpstan.neon index 71a6bb7c5a..96977def5f 100644 --- a/e2e/composer-version-config-invalid/phpstan.neon +++ b/e2e/composer-version-config-invalid/phpstan.neon @@ -1,5 +1,3 @@ -includes: - - ../../conf/bleedingEdge.neon parameters: phpVersion: min: 80303 diff --git a/e2e/composer-version-config-patch/phpstan.neon b/e2e/composer-version-config-patch/phpstan.neon deleted file mode 100644 index 49d50a5bac..0000000000 --- a/e2e/composer-version-config-patch/phpstan.neon +++ /dev/null @@ -1,2 +0,0 @@ -includes: - - ../../conf/bleedingEdge.neon diff --git a/e2e/composer-version-config/phpstan.neon b/e2e/composer-version-config/phpstan.neon index 04365f4376..003e5e1484 100644 --- a/e2e/composer-version-config/phpstan.neon +++ b/e2e/composer-version-config/phpstan.neon @@ -1,5 +1,3 @@ -includes: - - ../../conf/bleedingEdge.neon parameters: phpVersion: min: 80103 From 01796cff97a0db111bd76d18403201f41970a218 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 6 Nov 2024 15:27:04 +0100 Subject: [PATCH 16/21] Update e2e-tests.yml --- .github/workflows/e2e-tests.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 3f4694354e..9e2e481040 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -304,10 +304,22 @@ jobs: cd e2e/composer-min-open-end-version composer install ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/composer-min-version-v5 + composer install + ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/composer-min-version-v7 + composer install + ../../bin/phpstan analyze test.php --level=0 - script: | cd e2e/composer-min-version composer install ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/composer-no-versions + composer install + ../../bin/phpstan analyze test.php --level=0 - script: | cd e2e/composer-version-config composer install From 5d879c801a264fa1a2cd423836cf00be645fec8e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 6 Nov 2024 15:31:18 +0100 Subject: [PATCH 17/21] Update e2e-tests.yml --- .github/workflows/e2e-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 9e2e481040..30c12ca4b1 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -306,11 +306,11 @@ jobs: ../../bin/phpstan analyze test.php --level=0 - script: | cd e2e/composer-min-version-v5 - composer install + composer install --ignore-platform-reqs ../../bin/phpstan analyze test.php --level=0 - script: | cd e2e/composer-min-version-v7 - composer install + composer install --ignore-platform-reqs ../../bin/phpstan analyze test.php --level=0 - script: | cd e2e/composer-min-version From c6e325a84c70ee0a1fa6c952d96cc414459f3023 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 6 Nov 2024 15:40:10 +0100 Subject: [PATCH 18/21] check max php5 and php7 version --- src/Php/ComposerPhpVersionFactory.php | 16 +++++++++++----- src/Php/PhpVersionFactory.php | 2 ++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Php/ComposerPhpVersionFactory.php b/src/Php/ComposerPhpVersionFactory.php index 20fd3a52ad..ebcf7c130e 100644 --- a/src/Php/ComposerPhpVersionFactory.php +++ b/src/Php/ComposerPhpVersionFactory.php @@ -72,7 +72,7 @@ private function initializeVersions(): void $constraint = $parser->parseConstraints($composerPhpVersion); if (!$constraint->getLowerBound()->isZero()) { - $minVersion = $this->buildVersion($constraint->getLowerBound()->getVersion()); + $minVersion = $this->buildVersion($constraint->getLowerBound()->getVersion(), false); if ($minVersion !== null) { $this->minVersion = new PhpVersion($minVersion->getVersionId()); @@ -82,7 +82,7 @@ private function initializeVersions(): void return; } - $this->maxVersion = $this->buildVersion($constraint->getUpperBound()->getVersion()); + $this->maxVersion = $this->buildVersion($constraint->getUpperBound()->getVersion(), true); } public function getMinVersion(): ?PhpVersion @@ -132,9 +132,9 @@ private function getComposerRequireVersion(): ?string return $composerPhpVersion; } - private function buildVersion(string $minVersion): ?PhpVersion + private function buildVersion(string $version, bool $isMaxVersion): ?PhpVersion { - $matches = Strings::match($minVersion, '#^(\d+)\.(\d+)(?:\.(\d+))?#'); + $matches = Strings::match($version, '#^(\d+)\.(\d+)(?:\.(\d+))?#'); if ($matches === null) { return null; } @@ -144,7 +144,13 @@ private function buildVersion(string $minVersion): ?PhpVersion $patch = $matches[3] ?? 0; $versionId = (int) sprintf('%d%02d%02d', $major, $minor, $patch); - $versionId = min($versionId, PhpVersionFactory::MAX_PHP_VERSION); + if ($isMaxVersion && $version === '6.0.0.0-dev') { + $versionId = min($versionId, PhpVersionFactory::MAX_PHP5_VERSION); + } elseif ($isMaxVersion && $version === '8.0.0.0-dev') { + $versionId = min($versionId, PhpVersionFactory::MAX_PHP7_VERSION); + } else { + $versionId = min($versionId, PhpVersionFactory::MAX_PHP_VERSION); + } return new PhpVersion($versionId); } diff --git a/src/Php/PhpVersionFactory.php b/src/Php/PhpVersionFactory.php index 2f4b566481..bd1bfcabf5 100644 --- a/src/Php/PhpVersionFactory.php +++ b/src/Php/PhpVersionFactory.php @@ -12,6 +12,8 @@ final class PhpVersionFactory public const MIN_PHP_VERSION = 70100; public const MAX_PHP_VERSION = 80499; + public const MAX_PHP5_VERSION = 50699; + public const MAX_PHP7_VERSION = 70499; public function __construct( private ?int $versionId, From 2475a75ba6e033f6550aa1a57be64269ca076122 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 6 Nov 2024 15:41:43 +0100 Subject: [PATCH 19/21] Update test.php --- e2e/composer-min-version-v5/test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/composer-min-version-v5/test.php b/e2e/composer-min-version-v5/test.php index 5a90b6c6ba..2f652079a6 100644 --- a/e2e/composer-min-version-v5/test.php +++ b/e2e/composer-min-version-v5/test.php @@ -3,4 +3,4 @@ \PHPStan\Testing\assertType('int<50600, 50699>', PHP_VERSION_ID); \PHPStan\Testing\assertType('5', PHP_MAJOR_VERSION); \PHPStan\Testing\assertType('6', PHP_MINOR_VERSION); -\PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); +\PHPStan\Testing\assertType('int<0, 99>', PHP_RELEASE_VERSION); From ba46f13ae5f2ae05f434c02a8fcf2bf7a1f0bf23 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 6 Nov 2024 15:47:00 +0100 Subject: [PATCH 20/21] fix test --- .github/workflows/e2e-tests.yml | 12 ++++++++---- e2e/composer-version-config-patch/test.php | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 30c12ca4b1..56d7c72df4 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -320,16 +320,20 @@ jobs: cd e2e/composer-no-versions composer install ../../bin/phpstan analyze test.php --level=0 - - script: | - cd e2e/composer-version-config - composer install - ../../bin/phpstan analyze test.php --level=0 - script: | cd e2e/composer-version-config-invalid OUTPUT=$(../bashunit -a exit_code "1" ../../bin/phpstan) echo "$OUTPUT" ../bashunit -a contains 'Invalid configuration' "$OUTPUT" ../bashunit -a contains 'Invalid PHP version range: phpVersion.max should be greater or equal to phpVersion.min.' "$OUTPUT" + - script: | + cd e2e/composer-version-config-patch + composer install + ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/composer-version-config + composer install + ../../bin/phpstan analyze test.php --level=0 steps: - name: "Checkout" diff --git a/e2e/composer-version-config-patch/test.php b/e2e/composer-version-config-patch/test.php index fc3e9997ce..3f201eadab 100644 --- a/e2e/composer-version-config-patch/test.php +++ b/e2e/composer-version-config-patch/test.php @@ -1,7 +1,7 @@ ', PHP_VERSION_ID); +\PHPStan\Testing\assertType('int<80002, 80015>', PHP_VERSION_ID); \PHPStan\Testing\assertType('8', PHP_MAJOR_VERSION); \PHPStan\Testing\assertType('0', PHP_MINOR_VERSION); -\PHPStan\Testing\assertType('int<1, 15>', PHP_RELEASE_VERSION); +\PHPStan\Testing\assertType('int<2, 15>', PHP_RELEASE_VERSION); From d91760497463cd61697fdc427b6c3bf6cce69573 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 6 Nov 2024 15:58:40 +0100 Subject: [PATCH 21/21] Update e2e-tests.yml --- .github/workflows/e2e-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 56d7c72df4..7577e5c04f 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -328,7 +328,7 @@ jobs: ../bashunit -a contains 'Invalid PHP version range: phpVersion.max should be greater or equal to phpVersion.min.' "$OUTPUT" - script: | cd e2e/composer-version-config-patch - composer install + composer install --ignore-platform-reqs ../../bin/phpstan analyze test.php --level=0 - script: | cd e2e/composer-version-config