diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 60d5a23a406..f5989b740b8 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -307,6 +307,11 @@ jobs: echo "$OUTPUT" ../bashunit -a matches "Note: Using configuration file .+phpstan.neon." "$OUTPUT" ../bashunit -a contains 'Result cache not used because the metadata do not match: metaExtensions' "$OUTPUT" + - script: | + cd e2e/result-cache-restore-without-reflection + composer install + ../../bin/phpstan -vvv + ../../bin/phpstan -vvv - script: | cd e2e/bug-12606 export CONFIGTEST=test diff --git a/e2e/result-cache-restore-without-reflection/.gitignore b/e2e/result-cache-restore-without-reflection/.gitignore new file mode 100644 index 00000000000..8b7ef350326 --- /dev/null +++ b/e2e/result-cache-restore-without-reflection/.gitignore @@ -0,0 +1,2 @@ +/vendor +composer.lock diff --git a/e2e/result-cache-restore-without-reflection/composer.json b/e2e/result-cache-restore-without-reflection/composer.json new file mode 100644 index 00000000000..c0d4ee43781 --- /dev/null +++ b/e2e/result-cache-restore-without-reflection/composer.json @@ -0,0 +1,24 @@ +{ + "require-dev": { + "phpstan/phpstan-symfony": "@dev", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan-doctrine": "@dev", + "phpstan/phpstan-beberlei-assert": "@dev", + "phpstan/phpstan-phpunit": "@dev", + "phpstan/phpstan-webmozart-assert": "@dev", + "phpstan/phpstan-mockery": "@dev", + "phpstan/phpstan-nette": "@dev", + "phpstan/phpstan-dibi": "@dev", + "php-standard-library/phpstan-extension": "@dev" + }, + "autoload": { + "classmap": [ + "lib/" + ] + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + } + } +} diff --git a/e2e/result-cache-restore-without-reflection/lib/ThrowingSourceLocator.php b/e2e/result-cache-restore-without-reflection/lib/ThrowingSourceLocator.php new file mode 100644 index 00000000000..197e78e755d --- /dev/null +++ b/e2e/result-cache-restore-without-reflection/lib/ThrowingSourceLocator.php @@ -0,0 +1,25 @@ +getArgs(); $argType = $scope->getType($args[0]->value); if ($argType instanceof ConstantStringType) { + $funcCall = new FuncCall(new FullyQualified('class_exists'), [ + new Arg(new String_(ltrim($argType->getValue(), '\\'))), + ]); return $this->typeSpecifier->create( - new FuncCall(new FullyQualified('class_exists'), [ - new Arg(new String_(ltrim($argType->getValue(), '\\'))), - ]), + new AlwaysRememberedExpr($funcCall, new BooleanType(), new BooleanType()), new ConstantBooleanType(true), $context, $scope, diff --git a/tests/PHPStan/Analyser/data/do-not-remember-possibly-impure-function-values.php b/tests/PHPStan/Analyser/data/do-not-remember-possibly-impure-function-values.php index 5adbbc52200..158ab83dec7 100644 --- a/tests/PHPStan/Analyser/data/do-not-remember-possibly-impure-function-values.php +++ b/tests/PHPStan/Analyser/data/do-not-remember-possibly-impure-function-values.php @@ -109,3 +109,50 @@ function test(): void assertType('int', impure()); } } + +function testClassExistsRemembered(): void +{ + if (\class_exists('Bug8579RememberedA')) { + assertType('true', \class_exists('Bug8579RememberedA')); + } else { + assertType('bool', \class_exists('Bug8579RememberedA')); + } + + assertType('bool', \class_exists('Bug8579RememberedA')); +} + +function testClassExistsFalseNotRemembered(): void +{ + if (!\class_exists('Bug8579FalseNotRememberedA')) { + assertType('bool', \class_exists('Bug8579FalseNotRememberedA')); + } + + assertType('bool', \class_exists('Bug8579FalseNotRememberedA')); +} + +function testInterfaceExistsFalseNotRemembered(): void +{ + if (!\interface_exists('Bug8579FalseNotRememberedC')) { + assertType('bool', \interface_exists('Bug8579FalseNotRememberedC')); + } + + assertType('bool', \interface_exists('Bug8579FalseNotRememberedC')); +} + +function testTraitExistsFalseNotRemembered(): void +{ + if (!\trait_exists('Bug8579FalseNotRememberedD')) { + assertType('bool', \trait_exists('Bug8579FalseNotRememberedD')); + } + + assertType('bool', \trait_exists('Bug8579FalseNotRememberedD')); +} + +function testEnumExistsFalseNotRemembered(): void +{ + if (!\enum_exists('Bug8579FalseNotRememberedE')) { + assertType('bool', \enum_exists('Bug8579FalseNotRememberedE')); + } + + assertType('bool', \enum_exists('Bug8579FalseNotRememberedE')); +} diff --git a/tests/PHPStan/Rules/Classes/InstantiationDoNotRememberPossiblyImpureValuesRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationDoNotRememberPossiblyImpureValuesRuleTest.php new file mode 100644 index 00000000000..d40092f3932 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/InstantiationDoNotRememberPossiblyImpureValuesRuleTest.php @@ -0,0 +1,35 @@ + + */ +class InstantiationDoNotRememberPossiblyImpureValuesRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return self::getContainer()->getByType(InstantiationRule::class); + } + + public function testBug8579(): void + { + $this->analyse([__DIR__ . '/data/bug-8579.php'], []); + } + + public static function getAdditionalConfigFiles(): array + { + return array_merge( + parent::getAdditionalConfigFiles(), + [ + __DIR__ . '/doNotRememberPossiblyImpureValues.neon', + ], + ); + } + +} diff --git a/tests/PHPStan/Rules/Classes/data/bug-8579.php b/tests/PHPStan/Rules/Classes/data/bug-8579.php new file mode 100644 index 00000000000..018ab54678c --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-8579.php @@ -0,0 +1,13 @@ +