diff --git a/src/Type/ClassConstantAccessType.php b/src/Type/ClassConstantAccessType.php index e3be822f44..6d5d97c050 100644 --- a/src/Type/ClassConstantAccessType.php +++ b/src/Type/ClassConstantAccessType.php @@ -8,6 +8,7 @@ use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Traits\LateResolvableTypeTrait; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; +use function count; final class ClassConstantAccessType implements CompoundType, LateResolvableType { @@ -49,13 +50,52 @@ public function isResolvable(): bool return !TypeUtils::containsTemplateType($this->type); } - protected function getResult(): Type + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { if ($this->type->hasConstant($this->constantName)->yes()) { - return $this->type->getConstant($this->constantName)->getValueType(); + $valueType = $this->type->getConstant($this->constantName)->getValueType(); + return $otherType->isSuperTypeOf($valueType); + } + + return $otherType->isSuperTypeOf($this->resolve()); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult + { + if ($this->type->hasConstant($this->constantName)->yes()) { + $valueType = $this->type->getConstant($this->constantName)->getValueType(); + return $acceptingType->accepts($valueType, $strictTypes); + } + + $result = $this->resolve(); + + if ($result instanceof CompoundType) { + return $result->isAcceptedBy($acceptingType, $strictTypes); + } + + return $acceptingType->accepts($result, $strictTypes); + } + + protected function getResult(): Type + { + if (!$this->type->hasConstant($this->constantName)->yes()) { + return new ErrorType(); + } + + $constantReflection = $this->type->getConstant($this->constantName); + + $classReflections = $this->type->getObjectClassReflections(); + $isFinalClass = count($classReflections) === 1 && $classReflections[0]->isFinal(); + + if ($isFinalClass || $constantReflection->isFinal()) { + return $constantReflection->getValueType(); + } + + if (!$constantReflection->hasPhpDocType() && !$constantReflection->hasNativeType()) { + return new MixedType(); } - return new ErrorType(); + return $constantReflection->getValueType(); } /** @@ -89,7 +129,7 @@ public function traverseSimultaneously(Type $right, callable $cb): Type public function toPhpDocNode(): TypeNode { - return new ConstTypeNode(new ConstFetchNode('static', $this->constantName)); + return new ConstTypeNode(new ConstFetchNode((string) $this->type->toPhpDocNode(), $this->constantName)); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-13828.php b/tests/PHPStan/Analyser/nsrt/bug-13828.php index 551b694536..d854531cfc 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-13828.php +++ b/tests/PHPStan/Analyser/nsrt/bug-13828.php @@ -22,8 +22,8 @@ class BarBaz extends FooBar function test(FooBar $foo, BarBaz $bar): void { - assertType("'foo'", $foo->test()); - assertType("'bar'", $bar->test()); + assertType('mixed', $foo->test()); + assertType('mixed', $bar->test()); } final class FinalFoo @@ -146,7 +146,7 @@ public function test(): string function testUntypedConstant(WithUntypedConstant $foo): void { - assertType("'foo'", $foo->test()); + assertType('mixed', $foo->test()); } final class FinalChild extends FooBar diff --git a/tests/PHPStan/Analyser/nsrt/bug-14556.php b/tests/PHPStan/Analyser/nsrt/bug-14556.php new file mode 100644 index 0000000000..79af53005d --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-14556.php @@ -0,0 +1,33 @@ +test()); + assertType('mixed', $bar->test()); + assertType("'bar'", $baz->test()); +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-6989.php b/tests/PHPStan/Analyser/nsrt/bug-6989.php index 3ea5dbe4b3..9ce61ee636 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6989.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6989.php @@ -17,7 +17,7 @@ class MyClass */ public function myMethod(array $items1, array $items2, array $items3): array { - assertType('array{key: string}', $items1); + assertType('non-empty-array', $items1); assertType('array{key: string}', $items2); assertType('array{key: string}', $items3); @@ -40,7 +40,7 @@ class ParentClass extends MyClass */ public function myMethod2(array $items1, array $items2, array $items3, array $items4, array $items5): array { - assertType('array{different_key: string}', $items1); + assertType('non-empty-array', $items1); assertType('array{different_key: string}', $items2); assertType('array{key: string}', $items3); assertType('array{different_key: string}', $items4); diff --git a/tests/PHPStan/Type/TypeToPhpDocNodeTest.php b/tests/PHPStan/Type/TypeToPhpDocNodeTest.php index 3cf9e5f3fa..df83d84957 100644 --- a/tests/PHPStan/Type/TypeToPhpDocNodeTest.php +++ b/tests/PHPStan/Type/TypeToPhpDocNodeTest.php @@ -522,6 +522,18 @@ public static function dataToPhpDocNodeWithoutCheckingEquals(): iterable new ConstantFloatType(-0.0), '-0.0', ]; + + $reflectionProvider = self::createReflectionProvider(); + + yield [ + new ClassConstantAccessType(new StaticType($reflectionProvider->getClass(stdClass::class)), 'FOO'), + 'static::FOO', + ]; + + yield [ + new ClassConstantAccessType(new ObjectType('stdClass'), 'FOO'), + 'stdClass::FOO', + ]; } #[DataProvider('dataToPhpDocNodeWithoutCheckingEquals')]