diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index f4dfa47e6e..434cb423a2 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5485,7 +5485,7 @@ private function getConstantTypes(): array $constantTypes = []; foreach ($this->expressionTypes as $exprString => $typeHolder) { $expr = $typeHolder->getExpr(); - if (!$expr instanceof ConstFetch) { + if (!$expr instanceof ConstFetch && !$expr instanceof Expr\ClassConstFetch) { continue; } $constantTypes[$exprString] = $typeHolder; @@ -5520,7 +5520,7 @@ private function getNativeConstantTypes(): array $constantTypes = []; foreach ($this->nativeExpressionTypes as $exprString => $typeHolder) { $expr = $typeHolder->getExpr(); - if (!$expr instanceof ConstFetch) { + if (!$expr instanceof ConstFetch && !$expr instanceof Expr\ClassConstFetch) { continue; } $constantTypes[$exprString] = $typeHolder; diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 8a807ea745..1f0f4ced28 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1062,6 +1062,19 @@ private function processStmtNode( }); $this->processStmtNodesInternal($stmt, $classLikeStatements, $classScope, $storage, $classStatementsGatherer, $context); + foreach ($stmt->stmts as $classLikeStmt) { + if (!$classLikeStmt instanceof Node\Stmt\ClassConst || !$classLikeStmt->isPublic()) { + continue; + } + + foreach ($classLikeStmt->consts as $const) { + $scope = $scope->assignExpression( + new Expr\ClassConstFetch(new Name\FullyQualified($classReflection->getName()), $const->name), + $classScope->getType($const->value), + $classScope->getNativeType($const->value), + ); + } + } $this->callNodeCallback($nodeCallback, new ClassPropertiesNode($stmt, $this->readWritePropertiesExtensionProvider, $classStatementsGatherer->getProperties(), $classStatementsGatherer->getPropertyUsages(), $classStatementsGatherer->getMethodCalls(), $classStatementsGatherer->getReturnStatementsNodes(), $classStatementsGatherer->getPropertyAssigns(), $classReflection), $classScope, $storage); $this->callNodeCallback($nodeCallback, new ClassMethodsNode($stmt, $classStatementsGatherer->getMethods(), $classStatementsGatherer->getMethodCalls(), $classReflection), $classScope, $storage); $this->callNodeCallback($nodeCallback, new ClassConstantsNode($stmt, $classStatementsGatherer->getConstants(), $classStatementsGatherer->getConstantFetches(), $classReflection), $classScope, $storage); @@ -2212,10 +2225,17 @@ public function leaveNode(Node $node): ?ExistingArrayDimFetch if ($scope->getClassReflection() === null) { throw new ShouldNotHappenException(); } + $constType = $scope->getType($const->value); + $constNativeType = $scope->getNativeType($const->value); $scope = $scope->assignExpression( new Expr\ClassConstFetch(new Name\FullyQualified($scope->getClassReflection()->getName()), $const->name), - $scope->getType($const->value), - $scope->getNativeType($const->value), + $constType, + $constNativeType, + ); + $scope = $scope->assignExpression( + new Expr\ClassConstFetch(new Name('self'), $const->name), + $constType, + $constNativeType, ); } } elseif ($stmt instanceof Node\Stmt\EnumCase) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-14105.php b/tests/PHPStan/Analyser/nsrt/bug-14105.php new file mode 100644 index 0000000000..d03b1063d4 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-14105.php @@ -0,0 +1,36 @@ += 8.2 + +namespace Bug14105; + +use function PHPStan\Testing\assertType; + +final readonly class Foo +{ + const func = static function (int $num): array { + return ['num' => $num]; + }; +} + +final readonly class PrivateFoo +{ + private const func = static function (int $num): array { + return ['num' => $num]; + }; + + public static function testStatic(): void + { + assertType('Closure(int): array{num: int}', self::func); + } + + public function testNonStatic(): void + { + assertType('Closure(int): array{num: int}', self::func); + } +} + +const func = static function (int $num): array { + return ['num' => $num]; +}; + +assertType('Closure(int): array{num: int}', Foo::func); +assertType('Closure(int): array{num: int}', func); diff --git a/tests/PHPStan/Analyser/nsrt/initializer-expr-type-resolver.php b/tests/PHPStan/Analyser/nsrt/initializer-expr-type-resolver.php index 7a5daede04..82a6d52460 100644 --- a/tests/PHPStan/Analyser/nsrt/initializer-expr-type-resolver.php +++ b/tests/PHPStan/Analyser/nsrt/initializer-expr-type-resolver.php @@ -14,7 +14,7 @@ class Foo public function doFoo(): void { - assertType('*ERROR*', self::COALESCE_SPECIAL); // could be 42 + assertType('42', self::COALESCE_SPECIAL); assertType("0|1|2|'foo'", self::COALESCE); assertType("'bar'|'foo'|true", self::TERNARY_SHORT); assertType("'bar'|'foo'", self::TERNARY_FULL);