diff --git a/src/Node/ClassPropertiesNode.php b/src/Node/ClassPropertiesNode.php index 3b364646c5..bc152abbb6 100644 --- a/src/Node/ClassPropertiesNode.php +++ b/src/Node/ClassPropertiesNode.php @@ -256,9 +256,14 @@ private function getMethodsCalledFromConstructor( $calledOnType = $callScope->resolveTypeByName($methodCallNode->class); } + $inMethod = $callScope->getFunction(); + if (!$inMethod instanceof MethodReflection) { + continue; + } + $methodName = $methodCallNode->name->toString(); if (array_key_exists($methodName, $initializedProperties)) { - foreach ($this->getInitializedProperties($callScope, $initialInitializedProperties) as $propertyName => $isInitialized) { + foreach ($this->getInitializedProperties($callScope, $initializedProperties[$inMethod->getName()] ?? $initialInitializedProperties) as $propertyName => $isInitialized) { $initializedProperties[$methodName][$propertyName] = $initializedProperties[$methodName][$propertyName]->and($isInitialized); } continue; @@ -270,14 +275,10 @@ private function getMethodsCalledFromConstructor( if ($methodReflection->getDeclaringClass()->getName() !== $classReflection->getName()) { continue; } - $inMethod = $callScope->getFunction(); - if (!$inMethod instanceof MethodReflection) { - continue; - } if (!in_array($inMethod->getName(), $methods, true)) { continue; } - $initializedProperties[$methodName] = $this->getInitializedProperties($callScope, $initialInitializedProperties); + $initializedProperties[$methodName] = $this->getInitializedProperties($callScope, $initializedProperties[$inMethod->getName()] ?? $initialInitializedProperties); $methods[] = $methodName; } diff --git a/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php b/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php index a0bc9e31e1..198986be57 100644 --- a/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php +++ b/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php @@ -135,4 +135,9 @@ public function testAdditionalConstructorsExtension(): void ]); } + public function testEfabricaLatteBug(): void + { + $this->analyse([__DIR__ . '/data/efabrica-latte-bug.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/efabrica-latte-bug.php b/tests/PHPStan/Rules/Properties/data/efabrica-latte-bug.php new file mode 100644 index 0000000000..ce3c3226e2 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/efabrica-latte-bug.php @@ -0,0 +1,100 @@ += 7.4 + +namespace EfabricaLatteBug; + +use Nette\Utils\Finder; +use PHPStan\File\FileExcluder; +use SplFileInfo; + +final class AnalysedTemplatesRegistry +{ + private FileExcluder $fileExcluder; + + /** @var string[] */ + private array $analysedPaths = []; + + private bool $reportUnanalysedTemplates; + + /** @var array */ + private array $templateFiles = []; + + /** + * @param string[] $analysedPaths + */ + public function __construct(FileExcluder $fileExcluder, array $analysedPaths, bool $reportUnanalysedTemplates) + { + $this->fileExcluder = $fileExcluder; + $this->analysedPaths = $analysedPaths; + $this->reportUnanalysedTemplates = $reportUnanalysedTemplates; + foreach ($this->getExistingTemplates() as $file) { + $this->templateFiles[$file] = false; + } + } + + public function isExcludedFromAnalysing(string $path): bool + { + return $this->fileExcluder->isExcludedFromAnalysing($path); + } + + public function templateAnalysed(string $path): void + { + $path = realpath($path) ?: $path; + $this->templateFiles[$path] = true; + } + + /** + * @return string[] + */ + public function getExistingTemplates(): array + { + $files = []; + foreach ($this->analysedPaths as $analysedPath) { + if (!is_dir($analysedPath)) { + continue; + } + /** @var SplFileInfo $file */ + foreach (Finder::findFiles('*.latte')->from($analysedPath) as $file) { + $filePath = (string)$file; + if ($this->isExcludedFromAnalysing($filePath)) { + continue; + } + $files[] = $filePath; + } + } + $files = array_unique($files); + sort($files); + return $files; + } + + /** + * @return string[] + */ + public function getAnalysedTemplates(): array + { + return array_keys(array_filter($this->templateFiles, function (bool $val) { + return $val; + })); + } + + /** + * @return string[] + */ + public function getUnanalysedTemplates(): array + { + return array_keys(array_filter($this->templateFiles, function (bool $val) { + return !$val; + })); + } + + /** + * @return string[] + */ + public function getReportedUnanalysedTemplates(): array + { + if ($this->reportUnanalysedTemplates) { + return $this->getUnanalysedTemplates(); + } else { + return []; + } + } +}