Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
24 changes: 22 additions & 2 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add a test when the constant is private:

https://3v4l.org/qhTGS#veol

I wonder why this works before the PR already

https://phpstan.org/r/5e8b0fd5-fa52-4787-bcd5-cb25eed12508

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm gonna add a few more tests with fixes. Also, I think I've accidentally fixed this:

assertType('*ERROR*', self::COALESCE_SPECIAL); // could be 42

I'm not sure if this was supposed to be fixed? It would make sense, though. Now it correctly resolves to 42 and the *ERROR* assertion fails.

I wonder why this works before the PR already
phpstan.org/r/5e8b0fd5-fa52-4787-bcd5-cb25eed12508

It doesn't look like working to me. It prints Dumped type: Closure(int): array, it should print Dumped type: Closure(int): array{num: int}.

continue;
}

foreach ($classLikeStmt->consts as $const) {
$scope = $scope->assignExpression(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why we don't know the full closure type, whereever it is assigned/calculated before this PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already inferred it, but scope transitions dropped ClassConstFetch/self::CONST info, so lookup fell back to reflection and lost shape precision.

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);
Expand Down Expand Up @@ -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) {
Expand Down
36 changes: 36 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-14105.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php // lint >= 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);
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading