diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 297697ee78..3bd6db7556 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2119,11 +2119,13 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu if ($propertyReflection === null) { return new ErrorType(); } - $nativeType = $propertyReflection->getNativeType(); - if ($nativeType === null) { - return new ErrorType(); + + if (!$propertyReflection->hasNativeType()) { + return new MixedType(); } + $nativeType = $propertyReflection->getNativeType(); + return $this->getNullsafeShortCircuitingType($node->var, $nativeType); } @@ -2167,11 +2169,12 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu if ($propertyReflection === null) { return new ErrorType(); } - $nativeType = $propertyReflection->getNativeType(); - if ($nativeType === null) { - return new ErrorType(); + if (!$propertyReflection->hasNativeType()) { + return new MixedType(); } + $nativeType = $propertyReflection->getNativeType(); + if ($node->class instanceof Expr) { return $this->getNullsafeShortCircuitingType($node->class, $nativeType); } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 58b6f34df5..e91ca6748e 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5556,7 +5556,18 @@ static function (): void { $assignedExprType = $scope->getType($assignedExpr); $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); if ($propertyReflection->canChangeTypeAfterAssignment()) { - $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); + if ($propertyReflection->hasNativeType()) { + $propertyNativeType = $propertyReflection->getNativeType(); + if ($propertyNativeType->isSuperTypeOf($assignedExprType)->yes()) { + $assignedExprNativeType = $scope->getNativeType($assignedExpr); + if (!$propertyNativeType->isSuperTypeOf($assignedExprNativeType)->yes()) { + $assignedExprNativeType = $propertyNativeType; + } + $scope = $scope->assignExpression($var, $assignedExprType, $assignedExprNativeType); + } + } else { + $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); + } } $declaringClass = $propertyReflection->getDeclaringClass(); if ($declaringClass->hasNativeProperty($propertyName)) { @@ -5621,7 +5632,18 @@ static function (): void { $assignedExprType = $scope->getType($assignedExpr); $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); if ($propertyReflection !== null && $propertyReflection->canChangeTypeAfterAssignment()) { - $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); + if ($propertyReflection->hasNativeType()) { + $propertyNativeType = $propertyReflection->getNativeType(); + if ($propertyNativeType->isSuperTypeOf($assignedExprType)->yes()) { + $assignedExprNativeType = $scope->getNativeType($assignedExpr); + if (!$propertyNativeType->isSuperTypeOf($assignedExprNativeType)->yes()) { + $assignedExprNativeType = $propertyNativeType; + } + $scope = $scope->assignExpression($var, $assignedExprType, $assignedExprNativeType); + } + } else { + $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); + } } } else { // fallback diff --git a/src/Reflection/Annotations/AnnotationPropertyReflection.php b/src/Reflection/Annotations/AnnotationPropertyReflection.php index 9188ef7721..cc1994bc9a 100644 --- a/src/Reflection/Annotations/AnnotationPropertyReflection.php +++ b/src/Reflection/Annotations/AnnotationPropertyReflection.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\MixedType; use PHPStan\Type\Type; final class AnnotationPropertyReflection implements ExtendedPropertyReflection @@ -42,6 +43,26 @@ public function isPublic(): bool return true; } + public function hasPhpDocType(): bool + { + return true; + } + + public function getPhpDocType(): Type + { + return $this->readableType; + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return $this->readableType; diff --git a/src/Reflection/Dummy/ChangedTypePropertyReflection.php b/src/Reflection/Dummy/ChangedTypePropertyReflection.php index 07dc20ce68..f235b2e6e3 100644 --- a/src/Reflection/Dummy/ChangedTypePropertyReflection.php +++ b/src/Reflection/Dummy/ChangedTypePropertyReflection.php @@ -12,7 +12,7 @@ final class ChangedTypePropertyReflection implements WrapperPropertyReflection { - public function __construct(private ClassReflection $declaringClass, private ExtendedPropertyReflection $reflection, private Type $readableType, private Type $writableType) + public function __construct(private ClassReflection $declaringClass, private ExtendedPropertyReflection $reflection, private Type $readableType, private Type $writableType, private Type $phpDocType, private Type $nativeType) { } @@ -41,6 +41,26 @@ public function getDocComment(): ?string return $this->reflection->getDocComment(); } + public function hasPhpDocType(): bool + { + return $this->reflection->hasPhpDocType(); + } + + public function getPhpDocType(): Type + { + return $this->phpDocType; + } + + public function hasNativeType(): bool + { + return $this->reflection->hasNativeType(); + } + + public function getNativeType(): Type + { + return $this->nativeType; + } + public function getReadableType(): Type { return $this->readableType; diff --git a/src/Reflection/Dummy/DummyPropertyReflection.php b/src/Reflection/Dummy/DummyPropertyReflection.php index 40a48911e8..c2c6d4c768 100644 --- a/src/Reflection/Dummy/DummyPropertyReflection.php +++ b/src/Reflection/Dummy/DummyPropertyReflection.php @@ -37,6 +37,26 @@ public function isPublic(): bool return true; } + public function hasPhpDocType(): bool + { + return false; + } + + public function getPhpDocType(): Type + { + return new MixedType(); + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return new MixedType(); diff --git a/src/Reflection/ExtendedPropertyReflection.php b/src/Reflection/ExtendedPropertyReflection.php index c4a55163bb..63b6246dd3 100644 --- a/src/Reflection/ExtendedPropertyReflection.php +++ b/src/Reflection/ExtendedPropertyReflection.php @@ -3,6 +3,7 @@ namespace PHPStan\Reflection; use PHPStan\TrinaryLogic; +use PHPStan\Type\Type; /** * The purpose of this interface is to be able to @@ -25,6 +26,14 @@ interface ExtendedPropertyReflection extends PropertyReflection public const HOOK_SET = 'set'; + public function hasPhpDocType(): bool; + + public function getPhpDocType(): Type; + + public function hasNativeType(): bool; + + public function getNativeType(): Type; + public function isAbstract(): TrinaryLogic; public function isFinal(): TrinaryLogic; diff --git a/src/Reflection/Php/EnumPropertyReflection.php b/src/Reflection/Php/EnumPropertyReflection.php index 8a9a4eed28..a74bb419ff 100644 --- a/src/Reflection/Php/EnumPropertyReflection.php +++ b/src/Reflection/Php/EnumPropertyReflection.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\MixedType; use PHPStan\Type\Type; final class EnumPropertyReflection implements ExtendedPropertyReflection @@ -41,6 +42,26 @@ public function getDocComment(): ?string return null; } + public function hasPhpDocType(): bool + { + return false; + } + + public function getPhpDocType(): Type + { + return new MixedType(); + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return $this->type; diff --git a/src/Reflection/Php/SimpleXMLElementProperty.php b/src/Reflection/Php/SimpleXMLElementProperty.php index a8cfff2cb3..d354ae5fe2 100644 --- a/src/Reflection/Php/SimpleXMLElementProperty.php +++ b/src/Reflection/Php/SimpleXMLElementProperty.php @@ -10,6 +10,7 @@ use PHPStan\Type\BooleanType; use PHPStan\Type\FloatType; use PHPStan\Type\IntegerType; +use PHPStan\Type\MixedType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -44,6 +45,26 @@ public function isPublic(): bool return true; } + public function hasPhpDocType(): bool + { + return false; + } + + public function getPhpDocType(): Type + { + return new MixedType(); + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return $this->type; diff --git a/src/Reflection/Php/UniversalObjectCrateProperty.php b/src/Reflection/Php/UniversalObjectCrateProperty.php index 3382a49344..0a2f8faf35 100644 --- a/src/Reflection/Php/UniversalObjectCrateProperty.php +++ b/src/Reflection/Php/UniversalObjectCrateProperty.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\MixedType; use PHPStan\Type\Type; final class UniversalObjectCrateProperty implements ExtendedPropertyReflection @@ -40,6 +41,26 @@ public function isPublic(): bool return true; } + public function hasPhpDocType(): bool + { + return false; + } + + public function getPhpDocType(): Type + { + return new MixedType(); + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return $this->readableType; diff --git a/src/Reflection/ResolvedPropertyReflection.php b/src/Reflection/ResolvedPropertyReflection.php index d5ffc248c6..e964d99b5e 100644 --- a/src/Reflection/ResolvedPropertyReflection.php +++ b/src/Reflection/ResolvedPropertyReflection.php @@ -59,6 +59,26 @@ public function isPublic(): bool return $this->reflection->isPublic(); } + public function hasPhpDocType(): bool + { + return $this->reflection->hasPhpDocType(); + } + + public function getPhpDocType(): Type + { + return $this->reflection->getPhpDocType(); + } + + public function hasNativeType(): bool + { + return $this->reflection->hasNativeType(); + } + + public function getNativeType(): Type + { + return $this->reflection->getNativeType(); + } + public function getReadableType(): Type { $type = $this->readableType; diff --git a/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php index 151945e921..06069f8410 100644 --- a/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php @@ -79,8 +79,10 @@ private function transformPropertyWithStaticType(ClassReflection $declaringClass { $readableType = $this->transformStaticType($property->getReadableType()); $writableType = $this->transformStaticType($property->getWritableType()); + $phpDocType = $this->transformStaticType($property->getPhpDocType()); + $nativeType = $this->transformStaticType($property->getNativeType()); - return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType); + return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType, $phpDocType, $nativeType); } private function transformStaticType(Type $type): Type diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php index 4b843829ad..18beaf3f8e 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php @@ -74,8 +74,10 @@ private function transformPropertyWithStaticType(ClassReflection $declaringClass { $readableType = $this->transformStaticType($property->getReadableType()); $writableType = $this->transformStaticType($property->getWritableType()); + $phpDocType = $this->transformStaticType($property->getPhpDocType()); + $nativeType = $this->transformStaticType($property->getNativeType()); - return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType); + return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType, $phpDocType, $nativeType); } private function transformStaticType(Type $type): Type diff --git a/src/Reflection/Type/IntersectionTypePropertyReflection.php b/src/Reflection/Type/IntersectionTypePropertyReflection.php index b2d1551e5c..9976bab57d 100644 --- a/src/Reflection/Type/IntersectionTypePropertyReflection.php +++ b/src/Reflection/Type/IntersectionTypePropertyReflection.php @@ -80,6 +80,26 @@ public function getDocComment(): ?string return null; } + public function hasPhpDocType(): bool + { + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->hasPhpDocType()); + } + + public function getPhpDocType(): Type + { + return TypeCombinator::intersect(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getPhpDocType(), $this->properties)); + } + + public function hasNativeType(): bool + { + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->hasNativeType()); + } + + public function getNativeType(): Type + { + return TypeCombinator::intersect(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getNativeType(), $this->properties)); + } + public function getReadableType(): Type { return TypeCombinator::intersect(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getReadableType(), $this->properties)); diff --git a/src/Reflection/Type/UnionTypePropertyReflection.php b/src/Reflection/Type/UnionTypePropertyReflection.php index bc3c4f4411..24e2e91156 100644 --- a/src/Reflection/Type/UnionTypePropertyReflection.php +++ b/src/Reflection/Type/UnionTypePropertyReflection.php @@ -80,6 +80,26 @@ public function getDocComment(): ?string return null; } + public function hasPhpDocType(): bool + { + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->hasPhpDocType()); + } + + public function getPhpDocType(): Type + { + return TypeCombinator::union(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getPhpDocType(), $this->properties)); + } + + public function hasNativeType(): bool + { + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->hasNativeType()); + } + + public function getNativeType(): Type + { + return TypeCombinator::union(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getNativeType(), $this->properties)); + } + public function getReadableType(): Type { return TypeCombinator::union(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getReadableType(), $this->properties)); diff --git a/src/Reflection/WrappedExtendedPropertyReflection.php b/src/Reflection/WrappedExtendedPropertyReflection.php index 52e1571309..fe64beb0f0 100644 --- a/src/Reflection/WrappedExtendedPropertyReflection.php +++ b/src/Reflection/WrappedExtendedPropertyReflection.php @@ -4,6 +4,7 @@ use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\MixedType; use PHPStan\Type\Type; final class WrappedExtendedPropertyReflection implements ExtendedPropertyReflection @@ -38,6 +39,26 @@ public function getDocComment(): ?string return $this->property->getDocComment(); } + public function hasPhpDocType(): bool + { + return false; + } + + public function getPhpDocType(): Type + { + return new MixedType(); + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return $this->property->getReadableType(); diff --git a/src/Rules/Properties/FoundPropertyReflection.php b/src/Rules/Properties/FoundPropertyReflection.php index 36a286a4a0..19e77db7e0 100644 --- a/src/Rules/Properties/FoundPropertyReflection.php +++ b/src/Rules/Properties/FoundPropertyReflection.php @@ -59,6 +59,26 @@ public function getDocComment(): ?string return $this->originalPropertyReflection->getDocComment(); } + public function hasPhpDocType(): bool + { + return $this->originalPropertyReflection->hasPhpDocType(); + } + + public function getPhpDocType(): Type + { + return $this->originalPropertyReflection->getPhpDocType(); + } + + public function hasNativeType(): bool + { + return $this->originalPropertyReflection->hasNativeType(); + } + + public function getNativeType(): Type + { + return $this->originalPropertyReflection->getNativeType(); + } + public function getReadableType(): Type { return $this->readableType; @@ -104,16 +124,6 @@ public function isNative(): bool return $this->getNativeReflection() !== null; } - public function getNativeType(): ?Type - { - $reflection = $this->getNativeReflection(); - if ($reflection === null) { - return null; - } - - return $reflection->getNativeType(); - } - public function getNativeReflection(): ?PhpPropertyReflection { $reflection = $this->originalPropertyReflection; diff --git a/src/Type/ObjectShapePropertyReflection.php b/src/Type/ObjectShapePropertyReflection.php index 37a98fa9ba..d5fb99f546 100644 --- a/src/Type/ObjectShapePropertyReflection.php +++ b/src/Type/ObjectShapePropertyReflection.php @@ -44,6 +44,26 @@ public function getDocComment(): ?string return null; } + public function hasPhpDocType(): bool + { + return true; + } + + public function getPhpDocType(): Type + { + return $this->type; + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return $this->type; diff --git a/tests/PHPStan/Analyser/nsrt/bug-12393.php b/tests/PHPStan/Analyser/nsrt/bug-12393.php new file mode 100644 index 0000000000..5410dc2150 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12393.php @@ -0,0 +1,99 @@ +name = $plugin["name"]; + assertType('string', $this->name); + } + + /** + * @param mixed[] $plugin + */ + public function doFoo(array $plugin){ + $this->untypedName = $plugin["name"]; + assertType('mixed', $this->untypedName); + } + + public function doBar(int $i){ + $this->float = $i; + assertType('float', $this->float); + } + + public function doBaz(int $i){ + $this->untypedFloat = $i; + assertType('int', $this->untypedFloat); + } + + public function doLorem(): void + { + $this->a = ['a' => 1]; + assertType('array{a: 1}', $this->a); + } +} + +class HelloWorldStatic +{ + private static string $name; + + /** @var string */ + private static $untypedName; + + private static float $float; + + /** @var float */ + private static $untypedFloat; + + private static array $a; + + /** + * @param mixed[] $plugin + */ + public function __construct(array $plugin){ + self::$name = $plugin["name"]; + assertType('string', self::$name); + } + + /** + * @param mixed[] $plugin + */ + public function doFoo(array $plugin){ + self::$untypedName = $plugin["name"]; + assertType('mixed', self::$untypedName); + } + + public function doBar(int $i){ + self::$float = $i; + assertType('float', self::$float); + } + + public function doBaz(int $i){ + self::$untypedFloat = $i; + assertType('int', self::$untypedFloat); + } + + public function doLorem(): void + { + self::$a = ['a' => 1]; + assertType('array{a: 1}', self::$a); + } +}