From fac7a756f7f72f66a8ba9de77ceeecb866009d7f Mon Sep 17 00:00:00 2001 From: Matthieu Renard Date: Mon, 26 Aug 2024 16:30:06 +0200 Subject: [PATCH 1/3] Fix bug #9630 (?) --- src/Type/FileTypeMapper.php | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index 2ba6b7ae90..5cbd38493a 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -109,21 +109,35 @@ public function getResolvedPhpDoc( return $this->createResolvedPhpDocBlock($phpDocKey, $nameScopeMap[$nameScopeKey], $docComment, $fileName); } + $keyFileName = $fileName; if (!isset($this->inProcess[$fileName][$nameScopeKey])) { // wrong $fileName due to traits + if (!is_null($className)) { + $reflectionProvider = $this->reflectionProviderProvider->getReflectionProvider(); + if ($reflectionProvider->hasClass($className)) { + $classFileName = $reflectionProvider->getClass($className)->getFileName(); + $nameScopeKeyCandidate = $this->getNameScopeKey($classFileName, $className, $traitName, $functionName); + if (isset($this->inProcess[$classFileName][$nameScopeKeyCandidate])) { + $keyFileName = $classFileName; + $nameScopeKey = $nameScopeKeyCandidate; + } + } + } + } + if (!isset($this->inProcess[$keyFileName][$nameScopeKey])) { return ResolvedPhpDocBlock::createEmpty(); } - if ($this->inProcess[$fileName][$nameScopeKey] === true) { // PHPDoc has cyclic dependency + if ($this->inProcess[$keyFileName][$nameScopeKey] === true) { // PHPDoc has cyclic dependency return ResolvedPhpDocBlock::createEmpty(); } - if (is_callable($this->inProcess[$fileName][$nameScopeKey])) { - $resolveCallback = $this->inProcess[$fileName][$nameScopeKey]; - $this->inProcess[$fileName][$nameScopeKey] = true; - $this->inProcess[$fileName][$nameScopeKey] = $resolveCallback(); + if (is_callable($this->inProcess[$keyFileName][$nameScopeKey])) { + $resolveCallback = $this->inProcess[$keyFileName][$nameScopeKey]; + $this->inProcess[$keyFileName][$nameScopeKey] = true; + $this->inProcess[$keyFileName][$nameScopeKey] = $resolveCallback(); } - return $this->createResolvedPhpDocBlock($phpDocKey, $this->inProcess[$fileName][$nameScopeKey], $docComment, $fileName); + return $this->createResolvedPhpDocBlock($phpDocKey, $this->inProcess[$keyFileName][$nameScopeKey], $docComment, $fileName); } private function createResolvedPhpDocBlock(string $phpDocKey, NameScope $nameScope, string $phpDocString, ?string $fileName): ResolvedPhpDocBlock From 6e4083683ae8087cf19c4b89df7f22767470bf90 Mon Sep 17 00:00:00 2001 From: Matthieu Renard Date: Wed, 28 Aug 2024 11:29:01 +0200 Subject: [PATCH 2/3] Adding two tests : one that passes here to show what has been fixed, and another one that shows a bug that may have appeared when fixing the other one. --- ...nericsWithMultipleFilesIntegrationTest.php | 84 +++++++++++++++++++ tests/PHPStan/Generics/data/bug9630/C1.php | 19 +++++ tests/PHPStan/Generics/data/bug9630/T1.php | 24 ++++++ tests/PHPStan/Generics/data/bug9630/T2.php | 19 +++++ tests/PHPStan/Generics/data/bug9630/main.php | 48 +++++++++++ tests/PHPStan/Generics/data/bug9630_2/C1.php | 19 +++++ tests/PHPStan/Generics/data/bug9630_2/T1.php | 24 ++++++ tests/PHPStan/Generics/data/bug9630_2/T2.php | 19 +++++ .../PHPStan/Generics/data/bug9630_2/main.php | 48 +++++++++++ 9 files changed, 304 insertions(+) create mode 100644 tests/PHPStan/Generics/GenericsWithMultipleFilesIntegrationTest.php create mode 100644 tests/PHPStan/Generics/data/bug9630/C1.php create mode 100644 tests/PHPStan/Generics/data/bug9630/T1.php create mode 100644 tests/PHPStan/Generics/data/bug9630/T2.php create mode 100644 tests/PHPStan/Generics/data/bug9630/main.php create mode 100644 tests/PHPStan/Generics/data/bug9630_2/C1.php create mode 100644 tests/PHPStan/Generics/data/bug9630_2/T1.php create mode 100644 tests/PHPStan/Generics/data/bug9630_2/T2.php create mode 100644 tests/PHPStan/Generics/data/bug9630_2/main.php diff --git a/tests/PHPStan/Generics/GenericsWithMultipleFilesIntegrationTest.php b/tests/PHPStan/Generics/GenericsWithMultipleFilesIntegrationTest.php new file mode 100644 index 0000000000..44b327f2f7 --- /dev/null +++ b/tests/PHPStan/Generics/GenericsWithMultipleFilesIntegrationTest.php @@ -0,0 +1,84 @@ +> + */ + public function dataTopics(): array + { + return [ + 'bug9630' => ['bug9630', [9]], + 'bug9630_2' => ['bug9630_2', [9]], + ]; + } + + /** + * @dataProvider dataTopics + * + * @param int[] $levels + */ + public function testDir(string $dir, array $levels): void + { + $dir = $this->getDataPath() . DIRECTORY_SEPARATOR . $dir; + $this->assertDirectoryIsReadable($dir); + $phpstanCmd = escapeshellcmd($this->getPhpStanExecutablePath()); + $configPath = $this->getPhpStanConfigPath(); + + $configOption = is_null($configPath) ? '' : '--configuration ' . escapeshellarg($configPath); + + exec(sprintf('%s %s clear-result-cache %s 2>&1', escapeshellcmd(PHP_BINARY), $phpstanCmd, $configOption), $clearResultCacheOutputLines, $clearResultCacheExitCode); + + if (0 !== $clearResultCacheExitCode) { + throw new ShouldNotHappenException('Could not clear result cache: ' . implode("\n", $clearResultCacheOutputLines)); + } + + putenv('__PHPSTAN_FORCE_VALIDATE_STUB_FILES=1'); + + foreach ($levels as $level) { + unset($outputLines); + + $toExec = sprintf('%s %s analyse --no-progress --error-format=prettyJson --level=%d %s %s', escapeshellcmd(PHP_BINARY), $phpstanCmd, $level, $configOption, escapeshellarg($dir)); + + exec($toExec, $outputLines); + + $output = implode("\n", $outputLines); + + try { + $actualJson = Json::decode($output, Json::FORCE_ARRAY); + } catch (JsonException) { + throw new JsonException(sprintf('Cannot decode: %s', $output)); + } + + // Check that there was no error during the execution of PHPStan + foreach ($actualJson['files'] as $file => $fileJson) { + $this->assertCount(0, $fileJson['messages'], 'The file ' . $file . ' contains errors. The command that produced the error was: ' . $toExec); + } + } + } + + public function getDataPath(): string + { + return __DIR__ . '/data'; + } + + public function getPhpStanExecutablePath(): string + { + return __DIR__ . '/../../../bin/phpstan'; + } + + public function getPhpStanConfigPath(): string + { + return __DIR__ . '/generics.neon'; + } +} diff --git a/tests/PHPStan/Generics/data/bug9630/C1.php b/tests/PHPStan/Generics/data/bug9630/C1.php new file mode 100644 index 0000000000..64dc09e552 --- /dev/null +++ b/tests/PHPStan/Generics/data/bug9630/C1.php @@ -0,0 +1,19 @@ + + */ +class C1 implements B +{ + /** + * @use T1 + */ + use T1; + + public function f(): ?A + { + return $this->getParam(new A2()); + } +} diff --git a/tests/PHPStan/Generics/data/bug9630/T1.php b/tests/PHPStan/Generics/data/bug9630/T1.php new file mode 100644 index 0000000000..8a18ca9ab5 --- /dev/null +++ b/tests/PHPStan/Generics/data/bug9630/T1.php @@ -0,0 +1,24 @@ +> + */ + use T2; + + /** + * @param T $p + * @return template-type + */ + public function getParam(A $p): ?A + { + return $this->getParamFromT2($p->getOther()); + } +} + diff --git a/tests/PHPStan/Generics/data/bug9630/T2.php b/tests/PHPStan/Generics/data/bug9630/T2.php new file mode 100644 index 0000000000..e5c1796239 --- /dev/null +++ b/tests/PHPStan/Generics/data/bug9630/T2.php @@ -0,0 +1,19 @@ + + */ +class A1 implements A +{ + public function getOther(): A2 + { + return new A2(); + } +} + +/** + * @implements A + */ +class A2 implements A +{ + public function getOther(): A1 + { + return new A1(); + } +} + +/** + * @template T of A + */ +interface B +{ + /** + * @return T|null + */ + public function f(): ?A; +} + diff --git a/tests/PHPStan/Generics/data/bug9630_2/C1.php b/tests/PHPStan/Generics/data/bug9630_2/C1.php new file mode 100644 index 0000000000..040561a857 --- /dev/null +++ b/tests/PHPStan/Generics/data/bug9630_2/C1.php @@ -0,0 +1,19 @@ + + */ +class C1 implements B +{ + /** + * @use T1 + */ + use T1; + + public function f(): ?A + { + return $this->getParam(new A2()); + } +} diff --git a/tests/PHPStan/Generics/data/bug9630_2/T1.php b/tests/PHPStan/Generics/data/bug9630_2/T1.php new file mode 100644 index 0000000000..da0bd2fb82 --- /dev/null +++ b/tests/PHPStan/Generics/data/bug9630_2/T1.php @@ -0,0 +1,24 @@ +> + */ + use T2; + + /** + * @param T $p + * @return template-type + */ + public function getParam(A $p): ?A + { + return $this->getParamFromT2($p->getOther()); + } +} + diff --git a/tests/PHPStan/Generics/data/bug9630_2/T2.php b/tests/PHPStan/Generics/data/bug9630_2/T2.php new file mode 100644 index 0000000000..3ba61d5f33 --- /dev/null +++ b/tests/PHPStan/Generics/data/bug9630_2/T2.php @@ -0,0 +1,19 @@ + + */ +class A1 implements A +{ + public function getOther(): A2 + { + return new A2(); + } +} + +/** + * @implements A + */ +class A2 implements A +{ + public function getOther(): A1 + { + return new A1(); + } +} + +/** + * @template T of A + */ +interface B +{ + /** + * @return T|null + */ + public function f(): ?A; +} + From 666ea2e0c1946e688cf8242c9f75a33261f2ad9b Mon Sep 17 00:00:00 2001 From: Matthieu Renard Date: Wed, 28 Aug 2024 11:45:44 +0200 Subject: [PATCH 3/3] Ran CS fixer --- src/Type/FileTypeMapper.php | 1 + .../GenericsWithMultipleFilesIntegrationTest.php | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index 5cbd38493a..bcbb3c1282 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -32,6 +32,7 @@ use function is_array; use function is_callable; use function is_file; +use function is_null; use function ltrim; use function md5; use function sprintf; diff --git a/tests/PHPStan/Generics/GenericsWithMultipleFilesIntegrationTest.php b/tests/PHPStan/Generics/GenericsWithMultipleFilesIntegrationTest.php index 44b327f2f7..f29d56e056 100644 --- a/tests/PHPStan/Generics/GenericsWithMultipleFilesIntegrationTest.php +++ b/tests/PHPStan/Generics/GenericsWithMultipleFilesIntegrationTest.php @@ -6,12 +6,22 @@ use Nette\Utils\JsonException; use PHPStan\ShouldNotHappenException; use PHPStan\Testing\PHPStanTestCase; +use function escapeshellarg; +use function escapeshellcmd; +use function exec; +use function implode; +use function is_null; +use function putenv; +use function sprintf; +use const DIRECTORY_SEPARATOR; +use const PHP_BINARY; /** * @group genericsmulti */ class GenericsWithMultipleFilesIntegrationTest extends PHPStanTestCase { + /** * @return array> */ @@ -39,7 +49,7 @@ public function testDir(string $dir, array $levels): void exec(sprintf('%s %s clear-result-cache %s 2>&1', escapeshellcmd(PHP_BINARY), $phpstanCmd, $configOption), $clearResultCacheOutputLines, $clearResultCacheExitCode); - if (0 !== $clearResultCacheExitCode) { + if ($clearResultCacheExitCode !== 0) { throw new ShouldNotHappenException('Could not clear result cache: ' . implode("\n", $clearResultCacheOutputLines)); } @@ -81,4 +91,5 @@ public function getPhpStanConfigPath(): string { return __DIR__ . '/generics.neon'; } + }