diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php
index 16a9c15f05..ce7b62cb17 100644
--- a/src/Analyser/MutatingScope.php
+++ b/src/Analyser/MutatingScope.php
@@ -4287,7 +4287,7 @@ public function assignInitializedProperty(Type $fetchedOnType, string $propertyN
 			return $this;
 		}
 
-		$propertyReflection = $this->getPropertyReflection($fetchedOnType, $propertyName);
+		$propertyReflection = $this->getInstancePropertyReflection($fetchedOnType, $propertyName);
 		if ($propertyReflection === null) {
 			return $this;
 		}
@@ -6127,7 +6127,10 @@ private function methodCallReturnType(Type $typeWithMethod, string $methodName,
 		return $this->transformVoidToNull($parametersAcceptor->getReturnType(), $methodCall);
 	}
 
-	/** @api */
+	/**
+	 * @api
+	 * @deprecated Use getInstancePropertyReflection or getStaticPropertyReflection instead
+	 */
 	public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection
 	{
 		if ($typeWithProperty instanceof UnionType) {
@@ -6140,12 +6143,43 @@ public function getPropertyReflection(Type $typeWithProperty, string $propertyNa
 		return $typeWithProperty->getProperty($propertyName, $this);
 	}
 
+	/** @api */
+	public function getInstancePropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection
+	{
+		if ($typeWithProperty instanceof UnionType) {
+			$typeWithProperty = $typeWithProperty->filterTypes(static fn (Type $innerType) => $innerType->hasInstanceProperty($propertyName)->yes());
+		}
+		if (!$typeWithProperty->hasInstanceProperty($propertyName)->yes()) {
+			return null;
+		}
+
+		return $typeWithProperty->getInstanceProperty($propertyName, $this);
+	}
+
+	/** @api */
+	public function getStaticPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection
+	{
+		if ($typeWithProperty instanceof UnionType) {
+			$typeWithProperty = $typeWithProperty->filterTypes(static fn (Type $innerType) => $innerType->hasStaticProperty($propertyName)->yes());
+		}
+		if (!$typeWithProperty->hasStaticProperty($propertyName)->yes()) {
+			return null;
+		}
+
+		return $typeWithProperty->getStaticProperty($propertyName, $this);
+	}
+
 	/**
 	 * @param PropertyFetch|Node\Expr\StaticPropertyFetch $propertyFetch
 	 */
 	private function propertyFetchType(Type $fetchedOnType, string $propertyName, Expr $propertyFetch): ?Type
 	{
-		$propertyReflection = $this->getPropertyReflection($fetchedOnType, $propertyName);
+		if ($propertyFetch instanceof PropertyFetch) {
+			$propertyReflection = $this->getInstancePropertyReflection($fetchedOnType, $propertyName);
+		} else {
+			$propertyReflection = $this->getStaticPropertyReflection($fetchedOnType, $propertyName);
+		}
+
 		if ($propertyReflection === null) {
 			return null;
 		}
diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php
index b43f0298cc..7d41d88d90 100644
--- a/src/Analyser/NodeScopeResolver.php
+++ b/src/Analyser/NodeScopeResolver.php
@@ -3084,7 +3084,7 @@ static function (): void {
 			} else {
 				$propertyName = $expr->name->toString();
 				$propertyHolderType = $scopeBeforeVar->getType($expr->var);
-				$propertyReflection = $scopeBeforeVar->getPropertyReflection($propertyHolderType, $propertyName);
+				$propertyReflection = $scopeBeforeVar->getInstancePropertyReflection($propertyHolderType, $propertyName);
 				if ($propertyReflection !== null && $this->phpVersion->supportsPropertyHooks()) {
 					$propertyDeclaringClass = $propertyReflection->getDeclaringClass();
 					if ($propertyDeclaringClass->hasNativeProperty($propertyName)) {
@@ -5627,8 +5627,8 @@ static function (): void {
 			}
 
 			$propertyHolderType = $scope->getType($var->var);
-			if ($propertyName !== null && $propertyHolderType->hasProperty($propertyName)->yes()) {
-				$propertyReflection = $propertyHolderType->getProperty($propertyName, $scope);
+			if ($propertyName !== null && $propertyHolderType->hasInstanceProperty($propertyName)->yes()) {
+				$propertyReflection = $propertyHolderType->getInstanceProperty($propertyName, $scope);
 				$assignedExprType = $scope->getType($assignedExpr);
 				$nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope);
 				if ($propertyReflection->canChangeTypeAfterAssignment()) {
@@ -5716,7 +5716,7 @@ static function (): void {
 			$scope = $result->getScope();
 
 			if ($propertyName !== null) {
-				$propertyReflection = $scope->getPropertyReflection($propertyHolderType, $propertyName);
+				$propertyReflection = $scope->getStaticPropertyReflection($propertyHolderType, $propertyName);
 				$assignedExprType = $scope->getType($assignedExpr);
 				$nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope);
 				if ($propertyReflection !== null && $propertyReflection->canChangeTypeAfterAssignment()) {
diff --git a/src/Analyser/Scope.php b/src/Analyser/Scope.php
index 1134614b2f..c817f9e1a4 100644
--- a/src/Analyser/Scope.php
+++ b/src/Analyser/Scope.php
@@ -75,8 +75,13 @@ public function getMaybeDefinedVariables(): array;
 
 	public function hasConstant(Name $name): bool;
 
+	/** @deprecated Use getInstancePropertyReflection or getStaticPropertyReflection instead */
 	public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection;
 
+	public function getInstancePropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection;
+
+	public function getStaticPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection;
+
 	public function getMethodReflection(Type $typeWithMethod, string $methodName): ?ExtendedMethodReflection;
 
 	public function getConstantReflection(Type $typeWithConstant, string $constantName): ?ClassConstantReflection;
diff --git a/src/Dependency/DependencyResolver.php b/src/Dependency/DependencyResolver.php
index 77a4f957fe..3c32803347 100644
--- a/src/Dependency/DependencyResolver.php
+++ b/src/Dependency/DependencyResolver.php
@@ -253,7 +253,7 @@ public function resolveDependencies(Node $node, Scope $scope): NodeDependencies
 			}
 
 			if ($node->name instanceof Node\Identifier) {
-				$propertyReflection = $scope->getPropertyReflection($fetchedOnType, $node->name->toString());
+				$propertyReflection = $scope->getInstancePropertyReflection($fetchedOnType, $node->name->toString());
 				if ($propertyReflection !== null) {
 					$this->addClassToDependencies($propertyReflection->getDeclaringClass()->getName(), $dependenciesReflections);
 				}
@@ -369,13 +369,13 @@ public function resolveDependencies(Node $node, Scope $scope): NodeDependencies
 					$className = $scope->resolveName($node->class);
 					if ($this->reflectionProvider->hasClass($className)) {
 						$propertyClassReflection = $this->reflectionProvider->getClass($className);
-						if ($propertyClassReflection->hasProperty($node->name->toString())) {
-							$propertyReflection = $propertyClassReflection->getProperty($node->name->toString(), $scope);
+						if ($propertyClassReflection->hasStaticProperty($node->name->toString())) {
+							$propertyReflection = $propertyClassReflection->getStaticProperty($node->name->toString());
 							$this->addClassToDependencies($propertyReflection->getDeclaringClass()->getName(), $dependenciesReflections);
 						}
 					}
 				} else {
-					$propertyReflection = $scope->getPropertyReflection($scope->getType($node->class), $node->name->toString());
+					$propertyReflection = $scope->getStaticPropertyReflection($scope->getType($node->class), $node->name->toString());
 					if ($propertyReflection !== null) {
 						$this->addClassToDependencies($propertyReflection->getDeclaringClass()->getName(), $dependenciesReflections);
 					}
diff --git a/src/Node/ClassPropertiesNode.php b/src/Node/ClassPropertiesNode.php
index ec4dcba592..d114f34914 100644
--- a/src/Node/ClassPropertiesNode.php
+++ b/src/Node/ClassPropertiesNode.php
@@ -198,7 +198,7 @@ public function getUninitializedProperties(
 				continue;
 			}
 
-			$propertyReflection = $usageScope->getPropertyReflection($fetchedOnType, $propertyName);
+			$propertyReflection = $usageScope->getInstancePropertyReflection($fetchedOnType, $propertyName);
 			if ($propertyReflection === null) {
 				continue;
 			}
diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php
index f6e3da43e9..b3fdb56be0 100644
--- a/src/Reflection/ClassReflection.php
+++ b/src/Reflection/ClassReflection.php
@@ -82,6 +82,12 @@ final class ClassReflection
 	/** @var ExtendedPropertyReflection[] */
 	private array $properties = [];
 
+	/** @var ExtendedPropertyReflection[] */
+	private array $instanceProperties = [];
+
+	/** @var ExtendedPropertyReflection[] */
+	private array $staticProperties = [];
+
 	/** @var RealClassClassConstantReflection[] */
 	private array $constants = [];
 
@@ -148,6 +154,12 @@ final class ClassReflection
 	/** @var array<string, bool> */
 	private array $hasPropertyCache = [];
 
+	/** @var array<string, bool> */
+	private array $hasInstancePropertyCache = [];
+
+	/** @var array<string, bool> */
+	private array $hasStaticPropertyCache = [];
+
 	/**
 	 * @param PropertiesClassReflectionExtension[] $propertiesClassReflectionExtensions
 	 * @param MethodsClassReflectionExtension[] $methodsClassReflectionExtensions
@@ -462,6 +474,9 @@ private function allowsDynamicPropertiesExtensions(): bool
 		return false;
 	}
 
+	/**
+	 * @deprecated Use hasInstanceProperty or hasStaticProperty instead
+	 */
 	public function hasProperty(string $propertyName): bool
 	{
 		if (array_key_exists($propertyName, $this->hasPropertyCache)) {
@@ -481,6 +496,11 @@ public function hasProperty(string $propertyName): bool
 			}
 		}
 
+		// For BC purpose
+		if ($this->getPhpExtension()->hasStaticProperty($this, $propertyName)) {
+			return $this->hasPropertyCache[$propertyName] = true;
+		}
+
 		if ($this->requireExtendsPropertiesClassReflectionExtension->hasProperty($this, $propertyName)) {
 			return $this->hasPropertyCache[$propertyName] = true;
 		}
@@ -488,6 +508,49 @@ public function hasProperty(string $propertyName): bool
 		return $this->hasPropertyCache[$propertyName] = false;
 	}
 
+	public function hasInstanceProperty(string $propertyName): bool
+	{
+		if (array_key_exists($propertyName, $this->hasInstancePropertyCache)) {
+			return $this->hasInstancePropertyCache[$propertyName];
+		}
+
+		if ($this->isEnum()) {
+			return $this->hasInstancePropertyCache[$propertyName] = $this->hasNativeProperty($propertyName);
+		}
+
+		foreach ($this->propertiesClassReflectionExtensions as $i => $extension) {
+			if ($i > 0 && !$this->allowsDynamicPropertiesExtensions()) {
+				break;
+			}
+			if ($extension->hasProperty($this, $propertyName)) {
+				return $this->hasInstancePropertyCache[$propertyName] = true;
+			}
+		}
+
+		if ($this->requireExtendsPropertiesClassReflectionExtension->hasInstanceProperty($this, $propertyName)) {
+			return $this->hasPropertyCache[$propertyName] = true;
+		}
+
+		return $this->hasPropertyCache[$propertyName] = false;
+	}
+
+	public function hasStaticProperty(string $propertyName): bool
+	{
+		if (array_key_exists($propertyName, $this->hasStaticPropertyCache)) {
+			return $this->hasStaticPropertyCache[$propertyName];
+		}
+
+		if ($this->getPhpExtension()->hasStaticProperty($this, $propertyName)) {
+			return $this->hasStaticPropertyCache[$propertyName] = true;
+		}
+
+		if ($this->requireExtendsPropertiesClassReflectionExtension->hasStaticProperty($this, $propertyName)) {
+			return $this->hasStaticPropertyCache[$propertyName] = true;
+		}
+
+		return $this->hasStaticPropertyCache[$propertyName] = false;
+	}
+
 	public function hasMethod(string $methodName): bool
 	{
 		if (array_key_exists($methodName, $this->hasMethodCache)) {
@@ -632,6 +695,20 @@ public function evictPrivateSymbols(): void
 
 			unset($this->properties[$name]);
 		}
+		foreach ($this->instanceProperties as $name => $property) {
+			if (!$property->isPrivate()) {
+				continue;
+			}
+
+			unset($this->instanceProperties[$name]);
+		}
+		foreach ($this->staticProperties as $name => $property) {
+			if (!$property->isPrivate()) {
+				continue;
+			}
+
+			unset($this->staticProperties[$name]);
+		}
 		foreach ($this->methods as $name => $method) {
 			if (!$method->isPrivate()) {
 				continue;
@@ -642,6 +719,7 @@ public function evictPrivateSymbols(): void
 		$this->getPhpExtension()->evictPrivateSymbols($this->getCacheKey());
 	}
 
+	/** @deprecated Use getInstanceProperty or getStaticProperty */
 	public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
 	{
 		if ($this->isEnum()) {
@@ -671,6 +749,15 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco
 			}
 		}
 
+		// For BC purpose
+		if ($this->getPhpExtension()->hasStaticProperty($this, $propertyName)) {
+			$property = $this->wrapExtendedProperty($this->getPhpExtension()->getStaticProperty($this, $propertyName));
+			if ($scope->canReadProperty($property)) {
+				return $this->properties[$key] = $property;
+			}
+			$this->properties[$key] = $property;
+		}
+
 		if (!isset($this->properties[$key])) {
 			if ($this->requireExtendsPropertiesClassReflectionExtension->hasProperty($this, $propertyName)) {
 				$property = $this->requireExtendsPropertiesClassReflectionExtension->getProperty($this, $propertyName);
@@ -685,9 +772,72 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco
 		return $this->properties[$key];
 	}
 
+	public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		if ($this->isEnum()) {
+			return $this->getNativeProperty($propertyName);
+		}
+
+		$key = $propertyName;
+		if ($scope->isInClass()) {
+			$key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey());
+		}
+
+		if (!isset($this->instanceProperties[$key])) {
+			foreach ($this->propertiesClassReflectionExtensions as $i => $extension) {
+				if ($i > 0 && !$this->allowsDynamicPropertiesExtensions()) {
+					break;
+				}
+
+				if (!$extension->hasProperty($this, $propertyName)) {
+					continue;
+				}
+
+				$property = $this->wrapExtendedProperty($extension->getProperty($this, $propertyName));
+				if ($scope->canReadProperty($property)) {
+					return $this->instanceProperties[$key] = $property;
+				}
+				$this->instanceProperties[$key] = $property;
+			}
+		}
+
+		if (!isset($this->instanceProperties[$key])) {
+			if ($this->requireExtendsPropertiesClassReflectionExtension->hasInstanceProperty($this, $propertyName)) {
+				$property = $this->requireExtendsPropertiesClassReflectionExtension->getInstanceProperty($this, $propertyName);
+				$this->instanceProperties[$key] = $property;
+			}
+		}
+
+		if (!isset($this->instanceProperties[$key])) {
+			throw new MissingPropertyFromReflectionException($this->getName(), $propertyName);
+		}
+
+		return $this->instanceProperties[$key];
+	}
+
+	public function getStaticProperty(string $propertyName): ExtendedPropertyReflection
+	{
+		$key = $propertyName;
+		if (isset($this->staticProperties[$key])) {
+			return $this->staticProperties[$key];
+		}
+
+		if ($this->getPhpExtension()->hasStaticProperty($this, $propertyName)) {
+			$property = $this->wrapExtendedProperty($this->getPhpExtension()->getStaticProperty($this, $propertyName));
+			return $this->staticProperties[$key] = $property;
+		}
+
+		if ($this->requireExtendsPropertiesClassReflectionExtension->hasStaticProperty($this, $propertyName)) {
+			$property = $this->requireExtendsPropertiesClassReflectionExtension->getStaticProperty($this, $propertyName);
+			return $this->staticProperties[$key] = $property;
+		}
+
+		throw new MissingPropertyFromReflectionException($this->getName(), $propertyName);
+	}
+
 	public function hasNativeProperty(string $propertyName): bool
 	{
-		return $this->getPhpExtension()->hasProperty($this, $propertyName);
+		return $this->getPhpExtension()->hasNativeProperty($this, $propertyName);
 	}
 
 	public function getNativeProperty(string $propertyName): PhpPropertyReflection
diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php
index fb935ac630..d74125e5bd 100644
--- a/src/Reflection/InitializerExprTypeResolver.php
+++ b/src/Reflection/InitializerExprTypeResolver.php
@@ -403,11 +403,11 @@ public function getType(Expr $expr, InitializerExprContext $context): Type
 
 		if ($expr instanceof PropertyFetch && $expr->name instanceof Identifier) {
 			$fetchedOnType = $this->getType($expr->var, $context);
-			if (!$fetchedOnType->hasProperty($expr->name->name)->yes()) {
+			if (!$fetchedOnType->hasInstanceProperty($expr->name->name)->yes()) {
 				return new ErrorType();
 			}
 
-			return $fetchedOnType->getProperty($expr->name->name, new OutOfClassScope())->getReadableType();
+			return $fetchedOnType->getInstanceProperty($expr->name->name, new OutOfClassScope())->getReadableType();
 		}
 
 		return new MixedType();
diff --git a/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php b/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php
index 4b21f92451..78b94e4d69 100644
--- a/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php
+++ b/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php
@@ -54,12 +54,12 @@ private function findProperty(ClassReflection $classReflection, string $property
 
 			$this->inProcess[$typeDescription][$propertyName] = true;
 
-			if (!$type->hasProperty($propertyName)->yes()) {
+			if (!$type->hasInstanceProperty($propertyName)->yes()) {
 				unset($this->inProcess[$typeDescription][$propertyName]);
 				continue;
 			}
 
-			$property = $type->getProperty($propertyName, new OutOfClassScope());
+			$property = $type->getInstanceProperty($propertyName, new OutOfClassScope());
 			unset($this->inProcess[$typeDescription][$propertyName]);
 
 			return $property;
diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php
index fc0dd70dbe..d9463b6dc8 100644
--- a/src/Reflection/Php/PhpClassReflectionExtension.php
+++ b/src/Reflection/Php/PhpClassReflectionExtension.php
@@ -72,6 +72,9 @@ final class PhpClassReflectionExtension
 	/** @var ExtendedPropertyReflection[][] */
 	private array $propertiesIncludingAnnotations = [];
 
+	/** @var ExtendedPropertyReflection[][] */
+	private array $staticPropertiesIncludingAnnotations = [];
+
 	/** @var PhpPropertyReflection[][] */
 	private array $nativeProperties = [];
 
@@ -119,6 +122,17 @@ public function evictPrivateSymbols(string $classCacheKey): void
 				unset($this->propertiesIncludingAnnotations[$key][$name]);
 			}
 		}
+		foreach ($this->staticPropertiesIncludingAnnotations as $key => $properties) {
+			if ($key !== $classCacheKey) {
+				continue;
+			}
+			foreach ($properties as $name => $property) {
+				if (!$property->isPrivate()) {
+					continue;
+				}
+				unset($this->staticPropertiesIncludingAnnotations[$key][$name]);
+			}
+		}
 		foreach ($this->nativeProperties as $key => $properties) {
 			if ($key !== $classCacheKey) {
 				continue;
@@ -156,7 +170,10 @@ public function evictPrivateSymbols(string $classCacheKey): void
 
 	public function hasProperty(ClassReflection $classReflection, string $propertyName): bool
 	{
-		return $classReflection->getNativeReflection()->hasProperty($propertyName);
+		$nativeReflection = $classReflection->getNativeReflection();
+
+		return $nativeReflection->hasProperty($propertyName)
+			&& !$nativeReflection->getProperty($propertyName)->isStatic();
 	}
 
 	public function getProperty(ClassReflection $classReflection, string $propertyName): ExtendedPropertyReflection
@@ -168,6 +185,28 @@ public function getProperty(ClassReflection $classReflection, string $propertyNa
 		return $this->propertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName];
 	}
 
+	public function hasStaticProperty(ClassReflection $classReflection, string $propertyName): bool
+	{
+		$nativeReflection = $classReflection->getNativeReflection();
+
+		return $nativeReflection->hasProperty($propertyName)
+			&& $nativeReflection->getProperty($propertyName)->isStatic();
+	}
+
+	public function getStaticProperty(ClassReflection $classReflection, string $propertyName): ExtendedPropertyReflection
+	{
+		if (!isset($this->staticPropertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName])) {
+			$this->staticPropertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName] = $this->createProperty($classReflection, $propertyName, true);
+		}
+
+		return $this->staticPropertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName];
+	}
+
+	public function hasNativeProperty(ClassReflection $classReflection, string $propertyName): bool
+	{
+		return $classReflection->getNativeReflection()->hasProperty($propertyName);
+	}
+
 	public function getNativeProperty(ClassReflection $classReflection, string $propertyName): PhpPropertyReflection
 	{
 		if (!isset($this->nativeProperties[$classReflection->getCacheKey()][$propertyName])) {
diff --git a/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php b/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php
index 550a7bee59..27b51f7fdd 100644
--- a/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php
+++ b/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php
@@ -5,20 +5,33 @@
 use PHPStan\Analyser\OutOfClassScope;
 use PHPStan\Reflection\ClassReflection;
 use PHPStan\Reflection\ExtendedPropertyReflection;
-use PHPStan\Reflection\PropertiesClassReflectionExtension;
 use PHPStan\ShouldNotHappenException;
+use PHPStan\TrinaryLogic;
+use PHPStan\Type\Type;
 
-final class RequireExtendsPropertiesClassReflectionExtension implements PropertiesClassReflectionExtension
+final class RequireExtendsPropertiesClassReflectionExtension
 {
 
+	/** @deprecated Use hasInstanceProperty or hasStaticProperty */
 	public function hasProperty(ClassReflection $classReflection, string $propertyName): bool
 	{
-		return $this->findProperty($classReflection, $propertyName) !== null;
+		return $this->findProperty(
+			$classReflection,
+			$propertyName,
+			static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasProperty($propertyName),
+			static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getProperty($propertyName, new OutOfClassScope())
+		) !== null;
 	}
 
+	/** @deprecated Use getInstanceProperty or getStaticProperty */
 	public function getProperty(ClassReflection $classReflection, string $propertyName): ExtendedPropertyReflection
 	{
-		$property = $this->findProperty($classReflection, $propertyName);
+		$property = $this->findProperty(
+			$classReflection,
+			$propertyName,
+			static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasProperty($propertyName),
+			static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getProperty($propertyName, new OutOfClassScope())
+		);
 		if ($property === null) {
 			throw new ShouldNotHappenException();
 		}
@@ -26,7 +39,66 @@ public function getProperty(ClassReflection $classReflection, string $propertyNa
 		return $property;
 	}
 
-	private function findProperty(ClassReflection $classReflection, string $propertyName): ?ExtendedPropertyReflection
+	public function hasInstanceProperty(ClassReflection $classReflection, string $propertyName): bool
+	{
+		return $this->findProperty(
+			$classReflection,
+			$propertyName,
+			static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasInstanceProperty($propertyName),
+			static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getInstanceProperty($propertyName, new OutOfClassScope())
+		) !== null;
+	}
+
+	public function getInstanceProperty(ClassReflection $classReflection, string $propertyName): ExtendedPropertyReflection
+	{
+		$property = $this->findProperty(
+			$classReflection,
+			$propertyName,
+			static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasInstanceProperty($propertyName),
+			static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getInstanceProperty($propertyName, new OutOfClassScope())
+		);
+		if ($property === null) {
+			throw new ShouldNotHappenException();
+		}
+
+		return $property;
+	}
+
+	public function hasStaticProperty(ClassReflection $classReflection, string $propertyName): bool
+	{
+		return $this->findProperty(
+			$classReflection,
+			$propertyName,
+			static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasStaticProperty($propertyName),
+			static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getStaticProperty($propertyName, new OutOfClassScope())
+		) !== null;
+	}
+
+	public function getStaticProperty(ClassReflection $classReflection, string $propertyName): ExtendedPropertyReflection
+	{
+		$property = $this->findProperty(
+			$classReflection,
+			$propertyName,
+			static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasStaticProperty($propertyName),
+			static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getStaticProperty($propertyName, new OutOfClassScope())
+		);
+		if ($property === null) {
+			throw new ShouldNotHappenException();
+		}
+
+		return $property;
+	}
+
+	/**
+	 * @param callable(Type, string): TrinaryLogic               $propertyHasser
+	 * @param callable(Type, string): ExtendedPropertyReflection $propertyGetter
+	 */
+	private function findProperty(
+		ClassReflection $classReflection,
+		string $propertyName,
+		callable $propertyHasser,
+		callable $propertyGetter,
+	): ?ExtendedPropertyReflection
 	{
 		if (!$classReflection->isInterface()) {
 			return null;
@@ -36,16 +108,16 @@ private function findProperty(ClassReflection $classReflection, string $property
 		foreach ($requireExtendsTags as $requireExtendsTag) {
 			$type = $requireExtendsTag->getType();
 
-			if (!$type->hasProperty($propertyName)->yes()) {
+			if (!$propertyHasser($type, $propertyName)->yes()) {
 				continue;
 			}
 
-			return $type->getProperty($propertyName, new OutOfClassScope());
+			return $propertyGetter($type, $propertyName);
 		}
 
 		$interfaces = $classReflection->getInterfaces();
 		foreach ($interfaces as $interface) {
-			$property = $this->findProperty($interface, $propertyName);
+			$property = $this->findProperty($interface, $propertyName, $propertyHasser, $propertyGetter);
 			if ($property !== null) {
 				return $property;
 			}
diff --git a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php
index 239e732056..ed17441c78 100644
--- a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php
+++ b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php
@@ -176,7 +176,14 @@ public function processNode(Node $node, Scope $scope): array
 				if (!array_key_exists($propertyName, $properties)) {
 					continue;
 				}
-				$propertyReflection = $usageScope->getPropertyReflection($fetchedOnType, $propertyName);
+
+				$propertyNode = $properties[$propertyName]['node'];
+				if ($propertyNode->isStatic()) {
+					$propertyReflection = $usageScope->getStaticPropertyReflection($fetchedOnType, $propertyName);
+				} else {
+					$propertyReflection = $usageScope->getInstancePropertyReflection($fetchedOnType, $propertyName);
+				}
+
 				if ($propertyReflection === null) {
 					if (!$classType->isSuperTypeOf($fetchedOnType)->no()) {
 						if ($usage instanceof PropertyRead) {
diff --git a/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php b/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php
index 548e176c1d..70b1242690 100644
--- a/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php
+++ b/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php
@@ -36,17 +36,14 @@ public function processNode(Node $node, Scope $scope): array
 		}
 
 		$classType = $scope->resolveTypeByName($className);
-		if (!$classType->hasProperty($propertyName)->yes()) {
+		if (!$classType->hasStaticProperty($propertyName)->yes()) {
 			return [];
 		}
 
-		$property = $classType->getProperty($propertyName, $scope);
+		$property = $classType->getStaticProperty($propertyName, $scope);
 		if (!$property->isPrivate()) {
 			return [];
 		}
-		if (!$property->isStatic()) {
-			return [];
-		}
 
 		if ($scope->isInClass() && $scope->getClassReflection()->isFinal()) {
 			return [];
diff --git a/src/Rules/Properties/AccessPropertiesCheck.php b/src/Rules/Properties/AccessPropertiesCheck.php
index 467cf98a99..c8664dec8f 100644
--- a/src/Rules/Properties/AccessPropertiesCheck.php
+++ b/src/Rules/Properties/AccessPropertiesCheck.php
@@ -67,7 +67,7 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string
 			$scope,
 			NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $node->var),
 			sprintf('Access to property $%s on an unknown class %%s.', SprintfHelper::escapeFormatString($name)),
-			static fn (Type $type): bool => $type->canAccessProperties()->yes() && $type->hasProperty($name)->yes(),
+			static fn (Type $type): bool => $type->canAccessProperties()->yes() && $type->hasInstanceProperty($name)->yes(),
 		);
 		$type = $typeResult->getType();
 		if ($type instanceof ErrorType) {
@@ -93,7 +93,7 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string
 			];
 		}
 
-		$has = $type->hasProperty($name);
+		$has = $type->hasInstanceProperty($name);
 		if (!$has->no() && $this->canAccessUndefinedProperties($scope, $node)) {
 			return [];
 		}
@@ -124,12 +124,12 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string
 				$propertyClassReflection = $this->reflectionProvider->getClass($classNames[0]);
 				$parentClassReflection = $propertyClassReflection->getParentClass();
 				while ($parentClassReflection !== null) {
-					if ($parentClassReflection->hasProperty($name)) {
+					if ($parentClassReflection->hasInstanceProperty($name)) {
 						if ($write) {
-							if ($scope->canWriteProperty($parentClassReflection->getProperty($name, $scope))) {
+							if ($scope->canWriteProperty($parentClassReflection->getInstanceProperty($name, $scope))) {
 								return [];
 							}
-						} elseif ($scope->canReadProperty($parentClassReflection->getProperty($name, $scope))) {
+						} elseif ($scope->canReadProperty($parentClassReflection->getInstanceProperty($name, $scope))) {
 							return [];
 						}
 
@@ -157,6 +157,16 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string
 				}
 			}
 
+			if ($type->hasStaticProperty($name)->yes()) {
+				return [
+					RuleErrorBuilder::message(sprintf(
+						'Non-static access to static property %s::$%s.',
+						$type->getStaticProperty($name, $scope)->getDeclaringClass()->getDisplayName(),
+						$name,
+					))->identifier('staticProperty.nonStaticAccess')->build(),
+				];
+			}
+
 			$ruleErrorBuilder = RuleErrorBuilder::message(sprintf(
 				'Access to an undefined property %s::$%s.',
 				$typeForDescribe->describe(VerbosityLevel::typeOnly()),
@@ -173,17 +183,7 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string
 			];
 		}
 
-		$propertyReflection = $type->getProperty($name, $scope);
-		if ($propertyReflection->isStatic()) {
-			return [
-				RuleErrorBuilder::message(sprintf(
-					'Non-static access to static property %s::$%s.',
-					$propertyReflection->getDeclaringClass()->getDisplayName(),
-					$name,
-				))->identifier('staticProperty.nonStaticAccess')->build(),
-			];
-		}
-
+		$propertyReflection = $type->getInstanceProperty($name, $scope);
 		if ($write) {
 			if ($scope->canWriteProperty($propertyReflection)) {
 				return [];
diff --git a/src/Rules/Properties/AccessStaticPropertiesRule.php b/src/Rules/Properties/AccessStaticPropertiesRule.php
index 80f1034120..ad5f8092ea 100644
--- a/src/Rules/Properties/AccessStaticPropertiesRule.php
+++ b/src/Rules/Properties/AccessStaticPropertiesRule.php
@@ -140,7 +140,7 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node,
 				$scope,
 				NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $node->class),
 				sprintf('Access to static property $%s on an unknown class %%s.', SprintfHelper::escapeFormatString($name)),
-				static fn (Type $type): bool => $type->canAccessProperties()->yes() && $type->hasProperty($name)->yes(),
+				static fn (Type $type): bool => $type->canAccessProperties()->yes() && $type->hasStaticProperty($name)->yes(),
 			);
 			$classType = $classTypeResult->getType();
 			if ($classType instanceof ErrorType) {
@@ -172,7 +172,7 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node,
 			]);
 		}
 
-		$has = $classType->hasProperty($name);
+		$has = $classType->hasStaticProperty($name);
 		if (!$has->no() && $scope->isUndefinedExpressionAllowed($node)) {
 			return [];
 		}
@@ -188,8 +188,8 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node,
 				$parentClassReflection = $propertyClassReflection->getParentClass();
 
 				while ($parentClassReflection !== null) {
-					if ($parentClassReflection->hasProperty($name)) {
-						if ($scope->canReadProperty($parentClassReflection->getProperty($name, $scope))) {
+					if ($parentClassReflection->hasStaticProperty($name)) {
+						if ($scope->canReadProperty($parentClassReflection->getStaticProperty($name))) {
 							return [];
 						}
 						return [
@@ -205,33 +205,33 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node,
 				}
 			}
 
-			return array_merge($messages, [
-				RuleErrorBuilder::message(sprintf(
-					'Access to an undefined static property %s::$%s.',
-					$typeForDescribe->describe(VerbosityLevel::typeOnly()),
-					$name,
-				))->identifier('staticProperty.notFound')->build(),
-			]);
-		}
-
-		$property = $classType->getProperty($name, $scope);
-		if (!$property->isStatic()) {
-			$hasPropertyTypes = TypeUtils::getHasPropertyTypes($classType);
-			foreach ($hasPropertyTypes as $hasPropertyType) {
-				if ($hasPropertyType->getPropertyName() === $name) {
-					return [];
+			if ($classType->hasInstanceProperty($name)->yes()) {
+				$hasPropertyTypes = TypeUtils::getHasPropertyTypes($classType);
+				foreach ($hasPropertyTypes as $hasPropertyType) {
+					if ($hasPropertyType->getPropertyName() === $name) {
+						return [];
+					}
 				}
+
+				return array_merge($messages, [
+					RuleErrorBuilder::message(sprintf(
+						'Static access to instance property %s::$%s.',
+						$classType->getInstanceProperty($name, $scope)->getDeclaringClass()->getDisplayName(),
+						$name,
+					))->identifier('property.staticAccess')->build(),
+				]);
 			}
 
 			return array_merge($messages, [
 				RuleErrorBuilder::message(sprintf(
-					'Static access to instance property %s::$%s.',
-					$property->getDeclaringClass()->getDisplayName(),
+					'Access to an undefined static property %s::$%s.',
+					$typeForDescribe->describe(VerbosityLevel::typeOnly()),
 					$name,
-				))->identifier('property.staticAccess')->build(),
+				))->identifier('staticProperty.notFound')->build(),
 			]);
 		}
 
+		$property = $classType->getStaticProperty($name, $scope);
 		if (!$scope->canReadProperty($property)) {
 			return array_merge($messages, [
 				RuleErrorBuilder::message(sprintf(
diff --git a/src/Rules/Properties/PropertyReflectionFinder.php b/src/Rules/Properties/PropertyReflectionFinder.php
index 67b2785fa9..809a15b1a0 100644
--- a/src/Rules/Properties/PropertyReflectionFinder.php
+++ b/src/Rules/Properties/PropertyReflectionFinder.php
@@ -31,7 +31,7 @@ public function findPropertyReflectionsFromNode($propertyFetch, Scope $scope): a
 			$reflections = [];
 			$propertyHolderType = $scope->getType($propertyFetch->var);
 			foreach ($names as $name) {
-				$reflection = $this->findPropertyReflection(
+				$reflection = $this->findInstancePropertyReflection(
 					$propertyHolderType,
 					$name,
 					$propertyFetch->name instanceof Expr ? $scope->filterByTruthyValue(new Expr\BinaryOp\Identical(
@@ -63,7 +63,7 @@ public function findPropertyReflectionsFromNode($propertyFetch, Scope $scope): a
 
 		$reflections = [];
 		foreach ($names as $name) {
-			$reflection = $this->findPropertyReflection(
+			$reflection = $this->findStaticPropertyReflection(
 				$propertyHolderType,
 				$name,
 				$propertyFetch->name instanceof Expr ? $scope->filterByTruthyValue(new Expr\BinaryOp\Identical(
@@ -89,13 +89,13 @@ public function findPropertyReflectionFromNode($propertyFetch, Scope $scope): ?F
 		if ($propertyFetch instanceof Node\Expr\PropertyFetch) {
 			$propertyHolderType = $scope->getType($propertyFetch->var);
 			if ($propertyFetch->name instanceof Node\Identifier) {
-				return $this->findPropertyReflection($propertyHolderType, $propertyFetch->name->name, $scope);
+				return $this->findInstancePropertyReflection($propertyHolderType, $propertyFetch->name->name, $scope);
 			}
 
 			$nameType = $scope->getType($propertyFetch->name);
 			$nameTypeConstantStrings = $nameType->getConstantStrings();
 			if (count($nameTypeConstantStrings) === 1) {
-				return $this->findPropertyReflection($propertyHolderType, $nameTypeConstantStrings[0]->getValue(), $scope);
+				return $this->findInstancePropertyReflection($propertyHolderType, $nameTypeConstantStrings[0]->getValue(), $scope);
 			}
 
 			return null;
@@ -111,16 +111,33 @@ public function findPropertyReflectionFromNode($propertyFetch, Scope $scope): ?F
 			$propertyHolderType = $scope->getType($propertyFetch->class);
 		}
 
-		return $this->findPropertyReflection($propertyHolderType, $propertyFetch->name->name, $scope);
+		return $this->findStaticPropertyReflection($propertyHolderType, $propertyFetch->name->name, $scope);
 	}
 
-	private function findPropertyReflection(Type $propertyHolderType, string $propertyName, Scope $scope): ?FoundPropertyReflection
+	private function findInstancePropertyReflection(Type $propertyHolderType, string $propertyName, Scope $scope): ?FoundPropertyReflection
 	{
-		if (!$propertyHolderType->hasProperty($propertyName)->yes()) {
+		if (!$propertyHolderType->hasInstanceProperty($propertyName)->yes()) {
 			return null;
 		}
 
-		$originalProperty = $propertyHolderType->getProperty($propertyName, $scope);
+		$originalProperty = $propertyHolderType->getInstanceProperty($propertyName, $scope);
+
+		return new FoundPropertyReflection(
+			$originalProperty,
+			$scope,
+			$propertyName,
+			$originalProperty->getReadableType(),
+			$originalProperty->getWritableType(),
+		);
+	}
+
+	private function findStaticPropertyReflection(Type $propertyHolderType, string $propertyName, Scope $scope): ?FoundPropertyReflection
+	{
+		if (!$propertyHolderType->hasStaticProperty($propertyName)->yes()) {
+			return null;
+		}
+
+		$originalProperty = $propertyHolderType->getStaticProperty($propertyName, $scope);
 
 		return new FoundPropertyReflection(
 			$originalProperty,
diff --git a/src/Type/Accessory/HasPropertyType.php b/src/Type/Accessory/HasPropertyType.php
index 71b6b42759..07dc069628 100644
--- a/src/Type/Accessory/HasPropertyType.php
+++ b/src/Type/Accessory/HasPropertyType.php
@@ -72,7 +72,10 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult
 
 	public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
 	{
-		return new IsSuperTypeOfResult($type->hasProperty($this->propertyName), []);
+		return new IsSuperTypeOfResult(
+			$type->hasInstanceProperty($this->propertyName)->or($type->hasStaticProperty($this->propertyName)),
+			[],
+		);
 	}
 
 	public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult
@@ -87,7 +90,10 @@ public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult
 			$limit = IsSuperTypeOfResult::createMaybe();
 		}
 
-		return $limit->and(new IsSuperTypeOfResult($otherType->hasProperty($this->propertyName), []));
+		return $limit->and(new IsSuperTypeOfResult(
+			$otherType->hasInstanceProperty($this->propertyName)->or($otherType->hasStaticProperty($this->propertyName)),
+			[],
+		));
 	}
 
 	public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult
@@ -120,6 +126,24 @@ public function hasProperty(string $propertyName): TrinaryLogic
 		return TrinaryLogic::createMaybe();
 	}
 
+	public function hasInstanceProperty(string $propertyName): TrinaryLogic
+	{
+		if ($this->propertyName === $propertyName) {
+			return TrinaryLogic::createYes();
+		}
+
+		return TrinaryLogic::createMaybe();
+	}
+
+	public function hasStaticProperty(string $propertyName): TrinaryLogic
+	{
+		if ($this->propertyName === $propertyName) {
+			return TrinaryLogic::createYes();
+		}
+
+		return TrinaryLogic::createMaybe();
+	}
+
 	public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
 	{
 		return [new TrivialParametersAcceptor()];
diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php
index 9a12b00fbb..37adc98017 100644
--- a/src/Type/ClosureType.php
+++ b/src/Type/ClosureType.php
@@ -315,6 +315,36 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
 		return $this->objectType->getUnresolvedPropertyPrototype($propertyName, $scope);
 	}
 
+	public function hasInstanceProperty(string $propertyName): TrinaryLogic
+	{
+		return $this->objectType->hasInstanceProperty($propertyName);
+	}
+
+	public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->objectType->getInstanceProperty($propertyName, $scope);
+	}
+
+	public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		return $this->objectType->getUnresolvedInstancePropertyPrototype($propertyName, $scope);
+	}
+
+	public function hasStaticProperty(string $propertyName): TrinaryLogic
+	{
+		return $this->objectType->hasStaticProperty($propertyName);
+	}
+
+	public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->objectType->getStaticProperty($propertyName, $scope);
+	}
+
+	public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		return $this->objectType->getUnresolvedStaticPropertyPrototype($propertyName, $scope);
+	}
+
 	public function canCallMethods(): TrinaryLogic
 	{
 		return $this->objectType->canCallMethods();
diff --git a/src/Type/Enum/EnumCaseObjectType.php b/src/Type/Enum/EnumCaseObjectType.php
index e3ae5e23f5..cfc0513612 100644
--- a/src/Type/Enum/EnumCaseObjectType.php
+++ b/src/Type/Enum/EnumCaseObjectType.php
@@ -8,6 +8,7 @@
 use PHPStan\PhpDocParser\Ast\Type\TypeNode;
 use PHPStan\Reflection\ClassMemberAccessAnswerer;
 use PHPStan\Reflection\ClassReflection;
+use PHPStan\Reflection\ExtendedPropertyReflection;
 use PHPStan\Reflection\Php\EnumPropertyReflection;
 use PHPStan\Reflection\Php\EnumUnresolvedPropertyPrototypeReflection;
 use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
@@ -127,10 +128,15 @@ public function tryRemove(Type $typeToRemove): ?Type
 	}
 
 	public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope);
+	}
+
+	public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
 	{
 		$classReflection = $this->getClassReflection();
 		if ($classReflection === null) {
-			return parent::getUnresolvedPropertyPrototype($propertyName, $scope);
+			return parent::getUnresolvedInstancePropertyPrototype($propertyName, $scope);
 
 		}
 		if ($propertyName === 'name') {
@@ -153,7 +159,22 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
 			}
 		}
 
-		return parent::getUnresolvedPropertyPrototype($propertyName, $scope);
+		return parent::getUnresolvedInstancePropertyPrototype($propertyName, $scope);
+	}
+
+	public function hasStaticProperty(string $propertyName): TrinaryLogic
+	{
+		return TrinaryLogic::createNo();
+	}
+
+	public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		throw new ShouldNotHappenException();
+	}
+
+	public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		throw new ShouldNotHappenException();
 	}
 
 	public function getBackingValueType(): ?Type
diff --git a/src/Type/Generic/GenericObjectType.php b/src/Type/Generic/GenericObjectType.php
index a2bdadd7ae..54bef86b29 100644
--- a/src/Type/Generic/GenericObjectType.php
+++ b/src/Type/Generic/GenericObjectType.php
@@ -232,6 +232,30 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
 		return $prototype->doNotResolveTemplateTypeMapToBounds();
 	}
 
+	public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty();
+	}
+
+	public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		$prototype = parent::getUnresolvedInstancePropertyPrototype($propertyName, $scope);
+
+		return $prototype->doNotResolveTemplateTypeMapToBounds();
+	}
+
+	public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty();
+	}
+
+	public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		$prototype = parent::getUnresolvedStaticPropertyPrototype($propertyName, $scope);
+
+		return $prototype->doNotResolveTemplateTypeMapToBounds();
+	}
+
 	public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
 	{
 		return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php
index 149536a573..1ea13f645a 100644
--- a/src/Type/IntersectionType.php
+++ b/src/Type/IntersectionType.php
@@ -533,6 +533,72 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
 		return new IntersectionTypeUnresolvedPropertyPrototypeReflection($propertyName, $propertyPrototypes);
 	}
 
+	public function hasInstanceProperty(string $propertyName): TrinaryLogic
+	{
+		return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->hasInstanceProperty($propertyName));
+	}
+
+	public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty();
+	}
+
+	public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		$propertyPrototypes = [];
+		foreach ($this->types as $type) {
+			if (!$type->hasInstanceProperty($propertyName)->yes()) {
+				continue;
+			}
+
+			$propertyPrototypes[] = $type->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->withFechedOnType($this);
+		}
+
+		$propertiesCount = count($propertyPrototypes);
+		if ($propertiesCount === 0) {
+			throw new ShouldNotHappenException();
+		}
+
+		if ($propertiesCount === 1) {
+			return $propertyPrototypes[0];
+		}
+
+		return new IntersectionTypeUnresolvedPropertyPrototypeReflection($propertyName, $propertyPrototypes);
+	}
+
+	public function hasStaticProperty(string $propertyName): TrinaryLogic
+	{
+		return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->hasStaticProperty($propertyName));
+	}
+
+	public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty();
+	}
+
+	public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		$propertyPrototypes = [];
+		foreach ($this->types as $type) {
+			if (!$type->hasStaticProperty($propertyName)->yes()) {
+				continue;
+			}
+
+			$propertyPrototypes[] = $type->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->withFechedOnType($this);
+		}
+
+		$propertiesCount = count($propertyPrototypes);
+		if ($propertiesCount === 0) {
+			throw new ShouldNotHappenException();
+		}
+
+		if ($propertiesCount === 1) {
+			return $propertyPrototypes[0];
+		}
+
+		return new IntersectionTypeUnresolvedPropertyPrototypeReflection($propertyName, $propertyPrototypes);
+	}
+
 	public function canCallMethods(): TrinaryLogic
 	{
 		return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->canCallMethods());
diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php
index 487a474827..ada8a8beae 100644
--- a/src/Type/MixedType.php
+++ b/src/Type/MixedType.php
@@ -405,6 +405,48 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
 		);
 	}
 
+	public function hasInstanceProperty(string $propertyName): TrinaryLogic
+	{
+		return TrinaryLogic::createYes();
+	}
+
+	public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty();
+	}
+
+	public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		$property = new DummyPropertyReflection();
+		return new CallbackUnresolvedPropertyPrototypeReflection(
+			$property,
+			$property->getDeclaringClass(),
+			false,
+			static fn (Type $type): Type => $type,
+		);
+	}
+
+	public function hasStaticProperty(string $propertyName): TrinaryLogic
+	{
+		return TrinaryLogic::createYes();
+	}
+
+	public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty();
+	}
+
+	public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		$property = new DummyPropertyReflection();
+		return new CallbackUnresolvedPropertyPrototypeReflection(
+			$property,
+			$property->getDeclaringClass(),
+			false,
+			static fn (Type $type): Type => $type,
+		);
+	}
+
 	public function canCallMethods(): TrinaryLogic
 	{
 		return TrinaryLogic::createYes();
diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php
index 518ffa8f4a..344d2950ed 100644
--- a/src/Type/NeverType.php
+++ b/src/Type/NeverType.php
@@ -138,6 +138,36 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
 		throw new ShouldNotHappenException();
 	}
 
+	public function hasInstanceProperty(string $propertyName): TrinaryLogic
+	{
+		return TrinaryLogic::createNo();
+	}
+
+	public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		throw new ShouldNotHappenException();
+	}
+
+	public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		throw new ShouldNotHappenException();
+	}
+
+	public function hasStaticProperty(string $propertyName): TrinaryLogic
+	{
+		return TrinaryLogic::createNo();
+	}
+
+	public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		throw new ShouldNotHappenException();
+	}
+
+	public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		throw new ShouldNotHappenException();
+	}
+
 	public function canCallMethods(): TrinaryLogic
 	{
 		return TrinaryLogic::createYes();
diff --git a/src/Type/NonexistentParentClassType.php b/src/Type/NonexistentParentClassType.php
index 0b91e093e6..1d5153f731 100644
--- a/src/Type/NonexistentParentClassType.php
+++ b/src/Type/NonexistentParentClassType.php
@@ -77,6 +77,36 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
 		throw new ShouldNotHappenException();
 	}
 
+	public function hasInstanceProperty(string $propertyName): TrinaryLogic
+	{
+		return TrinaryLogic::createNo();
+	}
+
+	public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		throw new ShouldNotHappenException();
+	}
+
+	public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		throw new ShouldNotHappenException();
+	}
+
+	public function hasStaticProperty(string $propertyName): TrinaryLogic
+	{
+		return TrinaryLogic::createNo();
+	}
+
+	public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		throw new ShouldNotHappenException();
+	}
+
+	public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		throw new ShouldNotHappenException();
+	}
+
 	public function canCallMethods(): TrinaryLogic
 	{
 		return TrinaryLogic::createNo();
diff --git a/src/Type/ObjectShapeType.php b/src/Type/ObjectShapeType.php
index 1a1beed6c0..c66897d79d 100644
--- a/src/Type/ObjectShapeType.php
+++ b/src/Type/ObjectShapeType.php
@@ -90,6 +90,21 @@ public function getObjectClassReflections(): array
 	}
 
 	public function hasProperty(string $propertyName): TrinaryLogic
+	{
+		return $this->hasInstanceProperty($propertyName);
+	}
+
+	public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->getInstanceProperty($propertyName, $scope);
+	}
+
+	public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope);
+	}
+
+	public function hasInstanceProperty(string $propertyName): TrinaryLogic
 	{
 		if (!array_key_exists($propertyName, $this->properties)) {
 			return TrinaryLogic::createNo();
@@ -102,12 +117,12 @@ public function hasProperty(string $propertyName): TrinaryLogic
 		return TrinaryLogic::createYes();
 	}
 
-	public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
 	{
-		return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty();
+		return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty();
 	}
 
-	public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
 	{
 		if (!array_key_exists($propertyName, $this->properties)) {
 			throw new ShouldNotHappenException();
@@ -122,6 +137,21 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
 		);
 	}
 
+	public function hasStaticProperty(string $propertyName): TrinaryLogic
+	{
+		return TrinaryLogic::createNo();
+	}
+
+	public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		throw new ShouldNotHappenException();
+	}
+
+	public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		throw new ShouldNotHappenException();
+	}
+
 	public function accepts(Type $type, bool $strictTypes): AcceptsResult
 	{
 		if ($type instanceof CompoundType) {
@@ -143,7 +173,7 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult
 		$result = AcceptsResult::createYes();
 		$scope = new OutOfClassScope();
 		foreach ($this->properties as $propertyName => $propertyType) {
-			$typeHasProperty = $type->hasProperty($propertyName);
+			$typeHasProperty = $type->hasInstanceProperty($propertyName);
 			$hasProperty = new AcceptsResult(
 				$typeHasProperty,
 				$typeHasProperty->yes() ? [] : [
@@ -159,16 +189,23 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult
 				if (in_array($propertyName, $this->optionalProperties, true)) {
 					continue;
 				}
+
 				return $hasProperty;
 			}
 			if ($hasProperty->maybe() && in_array($propertyName, $this->optionalProperties, true)) {
 				$hasProperty = AcceptsResult::createYes();
 			}
 
+			if (!$hasProperty->yes() && $type->hasStaticProperty($propertyName)->yes()) {
+				return new AcceptsResult(TrinaryLogic::createNo(), [
+					sprintf('Property %s::$%s is static.', $type->getStaticProperty($propertyName, $scope)->getDeclaringClass()->getDisplayName(), $propertyName),
+				]);
+			}
+
 			$result = $result->and($hasProperty);
 
 			try {
-				$otherProperty = $type->getProperty($propertyName, $scope);
+				$otherProperty = $type->getInstanceProperty($propertyName, $scope);
 			} catch (MissingPropertyFromReflectionException) {
 				return new AcceptsResult(
 					$result->result,
@@ -255,7 +292,7 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
 		$result = IsSuperTypeOfResult::createYes();
 		$scope = new OutOfClassScope();
 		foreach ($this->properties as $propertyName => $propertyType) {
-			$hasProperty = new IsSuperTypeOfResult($type->hasProperty($propertyName), []);
+			$hasProperty = new IsSuperTypeOfResult($type->hasInstanceProperty($propertyName), []);
 			if ($hasProperty->no()) {
 				if (in_array($propertyName, $this->optionalProperties, true)) {
 					continue;
@@ -269,7 +306,7 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
 			$result = $result->and($hasProperty);
 
 			try {
-				$otherProperty = $type->getProperty($propertyName, $scope);
+				$otherProperty = $type->getInstanceProperty($propertyName, $scope);
 			} catch (MissingPropertyFromReflectionException) {
 				return $result;
 			}
@@ -366,12 +403,12 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
 			$typeMap = TemplateTypeMap::createEmpty();
 			$scope = new OutOfClassScope();
 			foreach ($this->properties as $name => $propertyType) {
-				if ($receivedType->hasProperty($name)->no()) {
+				if ($receivedType->hasInstanceProperty($name)->no()) {
 					continue;
 				}
 
 				try {
-					$receivedProperty = $receivedType->getProperty($name, $scope);
+					$receivedProperty = $receivedType->getInstanceProperty($name, $scope);
 				} catch (MissingPropertyFromReflectionException) {
 					continue;
 				}
@@ -462,10 +499,10 @@ public function traverseSimultaneously(Type $right, callable $cb): Type
 
 		$scope = new OutOfClassScope();
 		foreach ($this->properties as $name => $propertyType) {
-			if (!$right->hasProperty($name)->yes()) {
+			if (!$right->hasInstanceProperty($name)->yes()) {
 				return $this;
 			}
-			$transformed = $cb($propertyType, $right->getProperty($name, $scope)->getReadableType());
+			$transformed = $cb($propertyType, $right->getInstanceProperty($name, $scope)->getReadableType());
 			if ($transformed !== $propertyType) {
 				$stillOriginal = false;
 			}
diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php
index e5b2540d7b..e0bf42a831 100644
--- a/src/Type/ObjectType.php
+++ b/src/Type/ObjectType.php
@@ -22,7 +22,6 @@
 use PHPStan\Reflection\ExtendedMethodReflection;
 use PHPStan\Reflection\ExtendedPropertyReflection;
 use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension;
-use PHPStan\Reflection\PropertyReflection;
 use PHPStan\Reflection\ReflectionProviderStaticAccessor;
 use PHPStan\Reflection\TrivialParametersAcceptor;
 use PHPStan\Reflection\Type\CalledOnTypeUnresolvedMethodPrototypeReflection;
@@ -97,6 +96,12 @@ class ObjectType implements TypeWithClassName, SubtractableType
 	/** @var array<string, array<string, array<string, UnresolvedPropertyPrototypeReflection>>> */
 	private static array $properties = [];
 
+	/** @var array<string, array<string, array<string, UnresolvedPropertyPrototypeReflection>>> */
+	private static array $instanceProperties = [];
+
+	/** @var array<string, array<string, array<string, UnresolvedPropertyPrototypeReflection>>> */
+	private static array $staticProperties = [];
+
 	/** @var array<string, array<string, self|null>> */
 	private static array $ancestors = [];
 
@@ -127,6 +132,8 @@ public static function resetCaches(): void
 		self::$superTypes = [];
 		self::$methods = [];
 		self::$properties = [];
+		self::$instanceProperties = [];
+		self::$staticProperties = [];
 		self::$ancestors = [];
 		self::$enumCases = [];
 	}
@@ -246,22 +253,170 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
 		);
 	}
 
-	public function getPropertyWithoutTransformingStatic(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection
+	public function hasInstanceProperty(string $propertyName): TrinaryLogic
 	{
-		$classReflection = $this->getNakedClassReflection();
+		$classReflection = $this->getClassReflection();
 		if ($classReflection === null) {
+			return TrinaryLogic::createMaybe();
+		}
+
+		if ($classReflection->hasInstanceProperty($propertyName)) {
+			return TrinaryLogic::createYes();
+		}
+
+		if ($classReflection->allowsDynamicProperties()) {
+			return TrinaryLogic::createMaybe();
+		}
+
+		if (!$classReflection->isFinal()) {
+			return TrinaryLogic::createMaybe();
+		}
+
+		return TrinaryLogic::createNo();
+	}
+
+	public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty();
+	}
+
+	public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		if (!$scope->isInClass()) {
+			$canAccessProperty = 'no';
+		} else {
+			$canAccessProperty = $scope->getClassReflection()->getName();
+		}
+		$description = $this->describeCache();
+
+		if (isset(self::$instanceProperties[$description][$propertyName][$canAccessProperty])) {
+			return self::$instanceProperties[$description][$propertyName][$canAccessProperty];
+		}
+
+		$nakedClassReflection = $this->getNakedClassReflection();
+		if ($nakedClassReflection === null) {
 			throw new ClassNotFoundException($this->className);
 		}
 
-		if (!$classReflection->hasProperty($propertyName)) {
-			$classReflection = $this->getClassReflection();
+		if ($nakedClassReflection->isEnum()) {
+			if (
+				$propertyName === 'name'
+				|| ($propertyName === 'value' && $nakedClassReflection->isBackedEnum())
+			) {
+				$properties = [];
+				foreach ($this->getEnumCases() as $enumCase) {
+					$properties[] = $enumCase->getUnresolvedInstancePropertyPrototype($propertyName, $scope);
+				}
+
+				if (count($properties) > 0) {
+					if (count($properties) === 1) {
+						return $properties[0];
+					}
+
+					return new UnionTypeUnresolvedPropertyPrototypeReflection($propertyName, $properties);
+				}
+			}
 		}
 
+		if (!$nakedClassReflection->hasNativeProperty($propertyName)) {
+			$nakedClassReflection = $this->getClassReflection();
+		}
+
+		if ($nakedClassReflection === null) {
+			throw new ClassNotFoundException($this->className);
+		}
+
+		$property = $nakedClassReflection->getInstanceProperty($propertyName, $scope);
+
+		$ancestor = $this->getAncestorWithClassName($property->getDeclaringClass()->getName());
+		$resolvedClassReflection = null;
+		if ($ancestor !== null && $ancestor->hasInstanceProperty($propertyName)->yes()) {
+			$resolvedClassReflection = $ancestor->getClassReflection();
+			if ($ancestor !== $this) {
+				$property = $ancestor->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getNakedProperty();
+			}
+		}
+		if ($resolvedClassReflection === null) {
+			$resolvedClassReflection = $property->getDeclaringClass();
+		}
+
+		return self::$instanceProperties[$description][$propertyName][$canAccessProperty] = new CalledOnTypeUnresolvedPropertyPrototypeReflection(
+			$property,
+			$resolvedClassReflection,
+			true,
+			$this,
+		);
+	}
+
+	public function hasStaticProperty(string $propertyName): TrinaryLogic
+	{
+		$classReflection = $this->getClassReflection();
 		if ($classReflection === null) {
+			return TrinaryLogic::createMaybe();
+		}
+
+		if ($classReflection->hasStaticProperty($propertyName)) {
+			return TrinaryLogic::createYes();
+		}
+
+		if (!$classReflection->isFinal()) {
+			return TrinaryLogic::createMaybe();
+		}
+
+		return TrinaryLogic::createNo();
+	}
+
+	public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty();
+	}
+
+	public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		if (!$scope->isInClass()) {
+			$canAccessProperty = 'no';
+		} else {
+			$canAccessProperty = $scope->getClassReflection()->getName();
+		}
+		$description = $this->describeCache();
+
+		if (isset(self::$staticProperties[$description][$propertyName][$canAccessProperty])) {
+			return self::$staticProperties[$description][$propertyName][$canAccessProperty];
+		}
+
+		$nakedClassReflection = $this->getNakedClassReflection();
+		if ($nakedClassReflection === null) {
+			throw new ClassNotFoundException($this->className);
+		}
+
+		if (!$nakedClassReflection->hasNativeProperty($propertyName)) {
+			$nakedClassReflection = $this->getClassReflection();
+		}
+
+		if ($nakedClassReflection === null) {
 			throw new ClassNotFoundException($this->className);
 		}
 
-		return $classReflection->getProperty($propertyName, $scope);
+		$property = $nakedClassReflection->getStaticProperty($propertyName);
+
+		$ancestor = $this->getAncestorWithClassName($property->getDeclaringClass()->getName());
+		$resolvedClassReflection = null;
+		if ($ancestor !== null && $ancestor->hasStaticProperty($propertyName)->yes()) {
+			$resolvedClassReflection = $ancestor->getClassReflection();
+			if ($ancestor !== $this) {
+				$property = $ancestor->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getNakedProperty();
+			}
+		}
+		if ($resolvedClassReflection === null) {
+			$resolvedClassReflection = $property->getDeclaringClass();
+		}
+
+		return self::$staticProperties[$description][$propertyName][$canAccessProperty] = new CalledOnTypeUnresolvedPropertyPrototypeReflection(
+			$property,
+			$resolvedClassReflection,
+			true,
+			$this,
+		);
 	}
 
 	public function getReferencedClasses(): array
diff --git a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php
index 51d8999c2f..d627cde8c4 100644
--- a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php
+++ b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php
@@ -163,7 +163,7 @@ private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $
 			}
 			foreach ($propertyTypes as $propertyType) {
 				$propertyName = $propertyType->getValue();
-				$hasProperty = $type->hasProperty($propertyName);
+				$hasProperty = $type->hasInstanceProperty($propertyName);
 				if ($hasProperty->maybe()) {
 					return $allowMaybe ? new MixedType() : null;
 				}
@@ -171,7 +171,7 @@ private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $
 					continue;
 				}
 
-				$returnTypes[] = $type->getProperty($propertyName, $scope)->getReadableType();
+				$returnTypes[] = $type->getInstanceProperty($propertyName, $scope)->getReadableType();
 			}
 		}
 
diff --git a/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php b/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php
index b988a7883a..05bdd01dc7 100644
--- a/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php
+++ b/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php
@@ -40,7 +40,7 @@ public function getThrowTypeFromStaticMethodCall(MethodReflection $methodReflect
 
 			$classReflection = $this->reflectionProvider->getClass($constantString->getValue());
 			foreach ($propertyType->getConstantStrings() as $constantPropertyString) {
-				if (!$classReflection->hasProperty($constantPropertyString->getValue())) {
+				if (!$classReflection->hasInstanceProperty($constantPropertyString->getValue())) {
 					return $methodReflection->getThrowType();
 				}
 			}
diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php
index 9db20e5b34..9a21560777 100644
--- a/src/Type/StaticType.php
+++ b/src/Type/StaticType.php
@@ -237,6 +237,70 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
 		);
 	}
 
+	public function hasInstanceProperty(string $propertyName): TrinaryLogic
+	{
+		return $this->getStaticObjectType()->hasInstanceProperty($propertyName);
+	}
+
+	public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty();
+	}
+
+	public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		$staticObject = $this->getStaticObjectType();
+		$nakedProperty = $staticObject->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getNakedProperty();
+
+		$ancestor = $this->getAncestorWithClassName($nakedProperty->getDeclaringClass()->getName());
+		$classReflection = null;
+		if ($ancestor !== null) {
+			$classReflection = $ancestor->getClassReflection();
+		}
+		if ($classReflection === null) {
+			$classReflection = $nakedProperty->getDeclaringClass();
+		}
+
+		return new CallbackUnresolvedPropertyPrototypeReflection(
+			$nakedProperty,
+			$classReflection,
+			false,
+			fn (Type $type): Type => $this->transformStaticType($type, $scope),
+		);
+	}
+
+	public function hasStaticProperty(string $propertyName): TrinaryLogic
+	{
+		return $this->getStaticObjectType()->hasStaticProperty($propertyName);
+	}
+
+	public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty();
+	}
+
+	public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		$staticObject = $this->getStaticObjectType();
+		$nakedProperty = $staticObject->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getNakedProperty();
+
+		$ancestor = $this->getAncestorWithClassName($nakedProperty->getDeclaringClass()->getName());
+		$classReflection = null;
+		if ($ancestor !== null) {
+			$classReflection = $ancestor->getClassReflection();
+		}
+		if ($classReflection === null) {
+			$classReflection = $nakedProperty->getDeclaringClass();
+		}
+
+		return new CallbackUnresolvedPropertyPrototypeReflection(
+			$nakedProperty,
+			$classReflection,
+			false,
+			fn (Type $type): Type => $this->transformStaticType($type, $scope),
+		);
+	}
+
 	public function canCallMethods(): TrinaryLogic
 	{
 		return $this->getStaticObjectType()->canCallMethods();
diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php
index 0ff25dc124..71678b77a4 100644
--- a/src/Type/StrictMixedType.php
+++ b/src/Type/StrictMixedType.php
@@ -135,6 +135,36 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
 		throw new ShouldNotHappenException();
 	}
 
+	public function hasInstanceProperty(string $propertyName): TrinaryLogic
+	{
+		return TrinaryLogic::createNo();
+	}
+
+	public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		throw new ShouldNotHappenException();
+	}
+
+	public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		throw new ShouldNotHappenException();
+	}
+
+	public function hasStaticProperty(string $propertyName): TrinaryLogic
+	{
+		return TrinaryLogic::createNo();
+	}
+
+	public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		throw new ShouldNotHappenException();
+	}
+
+	public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		throw new ShouldNotHappenException();
+	}
+
 	public function canCallMethods(): TrinaryLogic
 	{
 		return TrinaryLogic::createNo();
diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php
index 5eb703077f..335053c39a 100644
--- a/src/Type/Traits/LateResolvableTypeTrait.php
+++ b/src/Type/Traits/LateResolvableTypeTrait.php
@@ -113,6 +113,36 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
 		return $this->resolve()->getUnresolvedPropertyPrototype($propertyName, $scope);
 	}
 
+	public function hasInstanceProperty(string $propertyName): TrinaryLogic
+	{
+		return $this->resolve()->hasInstanceProperty($propertyName);
+	}
+
+	public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->resolve()->getInstanceProperty($propertyName, $scope);
+	}
+
+	public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		return $this->resolve()->getUnresolvedInstancePropertyPrototype($propertyName, $scope);
+	}
+
+	public function hasStaticProperty(string $propertyName): TrinaryLogic
+	{
+		return $this->resolve()->hasStaticProperty($propertyName);
+	}
+
+	public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->resolve()->getStaticProperty($propertyName, $scope);
+	}
+
+	public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		return $this->resolve()->getUnresolvedStaticPropertyPrototype($propertyName, $scope);
+	}
+
 	public function canCallMethods(): TrinaryLogic
 	{
 		return $this->resolve()->canCallMethods();
diff --git a/src/Type/Traits/MaybeObjectTypeTrait.php b/src/Type/Traits/MaybeObjectTypeTrait.php
index ff50c721d3..537f1d6b91 100644
--- a/src/Type/Traits/MaybeObjectTypeTrait.php
+++ b/src/Type/Traits/MaybeObjectTypeTrait.php
@@ -61,6 +61,48 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
 		);
 	}
 
+	public function hasInstanceProperty(string $propertyName): TrinaryLogic
+	{
+		return TrinaryLogic::createMaybe();
+	}
+
+	public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty();
+	}
+
+	public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		$property = new DummyPropertyReflection();
+		return new CallbackUnresolvedPropertyPrototypeReflection(
+			$property,
+			$property->getDeclaringClass(),
+			false,
+			static fn (Type $type): Type => $type,
+		);
+	}
+
+	public function hasStaticProperty(string $propertyName): TrinaryLogic
+	{
+		return TrinaryLogic::createMaybe();
+	}
+
+	public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty();
+	}
+
+	public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		$property = new DummyPropertyReflection();
+		return new CallbackUnresolvedPropertyPrototypeReflection(
+			$property,
+			$property->getDeclaringClass(),
+			false,
+			static fn (Type $type): Type => $type,
+		);
+	}
+
 	public function canCallMethods(): TrinaryLogic
 	{
 		return TrinaryLogic::createMaybe();
diff --git a/src/Type/Traits/NonObjectTypeTrait.php b/src/Type/Traits/NonObjectTypeTrait.php
index d16b86c9b1..0d55341b46 100644
--- a/src/Type/Traits/NonObjectTypeTrait.php
+++ b/src/Type/Traits/NonObjectTypeTrait.php
@@ -46,6 +46,36 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
 		throw new ShouldNotHappenException();
 	}
 
+	public function hasInstanceProperty(string $propertyName): TrinaryLogic
+	{
+		return TrinaryLogic::createNo();
+	}
+
+	public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		throw new ShouldNotHappenException();
+	}
+
+	public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		throw new ShouldNotHappenException();
+	}
+
+	public function hasStaticProperty(string $propertyName): TrinaryLogic
+	{
+		return TrinaryLogic::createNo();
+	}
+
+	public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		throw new ShouldNotHappenException();
+	}
+
+	public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		throw new ShouldNotHappenException();
+	}
+
 	public function canCallMethods(): TrinaryLogic
 	{
 		return TrinaryLogic::createNo();
diff --git a/src/Type/Traits/ObjectTypeTrait.php b/src/Type/Traits/ObjectTypeTrait.php
index 45fc20121f..40535c4018 100644
--- a/src/Type/Traits/ObjectTypeTrait.php
+++ b/src/Type/Traits/ObjectTypeTrait.php
@@ -72,6 +72,48 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
 		);
 	}
 
+	public function hasInstanceProperty(string $propertyName): TrinaryLogic
+	{
+		return TrinaryLogic::createMaybe();
+	}
+
+	public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty();
+	}
+
+	public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		$property = new DummyPropertyReflection();
+		return new CallbackUnresolvedPropertyPrototypeReflection(
+			$property,
+			$property->getDeclaringClass(),
+			false,
+			static fn (Type $type): Type => $type,
+		);
+	}
+
+	public function hasStaticProperty(string $propertyName): TrinaryLogic
+	{
+		return TrinaryLogic::createMaybe();
+	}
+
+	public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty();
+	}
+
+	public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		$property = new DummyPropertyReflection();
+		return new CallbackUnresolvedPropertyPrototypeReflection(
+			$property,
+			$property->getDeclaringClass(),
+			false,
+			static fn (Type $type): Type => $type,
+		);
+	}
+
 	public function canCallMethods(): TrinaryLogic
 	{
 		return TrinaryLogic::createYes();
diff --git a/src/Type/Type.php b/src/Type/Type.php
index 15886a053c..a2da132974 100644
--- a/src/Type/Type.php
+++ b/src/Type/Type.php
@@ -74,12 +74,27 @@ public function describe(VerbosityLevel $level): string;
 
 	public function canAccessProperties(): TrinaryLogic;
 
+	/** @deprecated Use hasInstanceProperty or hasStaticProperty instead */
 	public function hasProperty(string $propertyName): TrinaryLogic;
 
+	/** @deprecated Use getInstanceProperty or getStaticProperty instead */
 	public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection;
 
+	/** @deprecated Use getUnresolvedInstancePropertyPrototype or getUnresolvedStaticPropertyPrototype instead */
 	public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection;
 
+	public function hasInstanceProperty(string $propertyName): TrinaryLogic;
+
+	public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection;
+
+	public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection;
+
+	public function hasStaticProperty(string $propertyName): TrinaryLogic;
+
+	public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection;
+
+	public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection;
+
 	public function canCallMethods(): TrinaryLogic;
 
 	public function hasMethod(string $methodName): TrinaryLogic;
diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php
index 08d678152a..6967350d98 100644
--- a/src/Type/UnionType.php
+++ b/src/Type/UnionType.php
@@ -490,6 +490,72 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
 		return new UnionTypeUnresolvedPropertyPrototypeReflection($propertyName, $propertyPrototypes);
 	}
 
+	public function hasInstanceProperty(string $propertyName): TrinaryLogic
+	{
+		return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->hasInstanceProperty($propertyName));
+	}
+
+	public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty();
+	}
+
+	public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		$propertyPrototypes = [];
+		foreach ($this->types as $type) {
+			if (!$type->hasInstanceProperty($propertyName)->yes()) {
+				continue;
+			}
+
+			$propertyPrototypes[] = $type->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->withFechedOnType($this);
+		}
+
+		$propertiesCount = count($propertyPrototypes);
+		if ($propertiesCount === 0) {
+			throw new ShouldNotHappenException();
+		}
+
+		if ($propertiesCount === 1) {
+			return $propertyPrototypes[0];
+		}
+
+		return new UnionTypeUnresolvedPropertyPrototypeReflection($propertyName, $propertyPrototypes);
+	}
+
+	public function hasStaticProperty(string $propertyName): TrinaryLogic
+	{
+		return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->hasStaticProperty($propertyName));
+	}
+
+	public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
+	{
+		return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty();
+	}
+
+	public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
+	{
+		$propertyPrototypes = [];
+		foreach ($this->types as $type) {
+			if (!$type->hasStaticProperty($propertyName)->yes()) {
+				continue;
+			}
+
+			$propertyPrototypes[] = $type->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->withFechedOnType($this);
+		}
+
+		$propertiesCount = count($propertyPrototypes);
+		if ($propertiesCount === 0) {
+			throw new ShouldNotHappenException();
+		}
+
+		if ($propertiesCount === 1) {
+			return $propertyPrototypes[0];
+		}
+
+		return new UnionTypeUnresolvedPropertyPrototypeReflection($propertyName, $propertyPrototypes);
+	}
+
 	public function canCallMethods(): TrinaryLogic
 	{
 		return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->canCallMethods());
diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php
index 167cad5f8e..a6716b2ee0 100644
--- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php
+++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php
@@ -9118,10 +9118,6 @@ public function dataUnionProperties(): array
 				'UnionProperties\Bar|UnionProperties\Foo',
 				'$something->doSomething',
 			],
-			[
-				'UnionProperties\Bar|UnionProperties\Foo',
-				'$something::$doSomething',
-			],
 		];
 	}
 
diff --git a/tests/PHPStan/Analyser/nsrt/bug-nullsafe-prop-static-access.php b/tests/PHPStan/Analyser/nsrt/bug-nullsafe-prop-static-access.php
index 14d4ecf708..82639be3e9 100644
--- a/tests/PHPStan/Analyser/nsrt/bug-nullsafe-prop-static-access.php
+++ b/tests/PHPStan/Analyser/nsrt/bug-nullsafe-prop-static-access.php
@@ -26,5 +26,4 @@ function foo(?A $a): void
 	\PHPStan\Testing\assertType('string|null', $a?->b->get());
 
 	\PHPStan\Testing\assertType('int|null', $a?->b::$value);
-	\PHPStan\Testing\assertType('int|null', $a?->b->value);
 }
diff --git a/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php b/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php
index 35d8075f8b..d54337a07a 100644
--- a/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php
+++ b/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php
@@ -287,11 +287,11 @@ public function testProperties(string $className, array $properties): void
 		$scope->method('canWriteProperty')->willReturn(true);
 		foreach ($properties as $propertyName => $expectedPropertyData) {
 			$this->assertTrue(
-				$class->hasProperty($propertyName),
+				$class->hasInstanceProperty($propertyName),
 				sprintf('Class %s does not define property %s.', $className, $propertyName),
 			);
 
-			$property = $class->getProperty($propertyName, $scope);
+			$property = $class->getInstanceProperty($propertyName, $scope);
 			$this->assertSame(
 				$expectedPropertyData['class'],
 				$property->getDeclaringClass()->getName(),
diff --git a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php
index 48c4197868..0610a37549 100644
--- a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php
+++ b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php
@@ -38,6 +38,8 @@ public function dataDeprecatedAnnotations(): array
 					],
 					'property' => [
 						'foo' => null,
+					],
+					'staticProperty' => [
 						'staticFoo' => null,
 					],
 				],
@@ -56,6 +58,8 @@ public function dataDeprecatedAnnotations(): array
 					],
 					'property' => [
 						'deprecatedFoo' => null,
+					],
+					'staticProperty' => [
 						'deprecatedStaticFoo' => null,
 					],
 				],
@@ -112,7 +116,13 @@ public function testDeprecatedAnnotations(bool $deprecated, string $className, ?
 		}
 
 		foreach ($deprecatedAnnotations['property'] ?? [] as $propertyName => $deprecatedMessage) {
-			$propertyAnnotation = $class->getProperty($propertyName, $scope);
+			$propertyAnnotation = $class->getInstanceProperty($propertyName, $scope);
+			$this->assertSame($deprecated, $propertyAnnotation->isDeprecated()->yes());
+			$this->assertSame($deprecatedMessage, $propertyAnnotation->getDeprecatedDescription());
+		}
+
+		foreach ($deprecatedAnnotations['staticProperty'] ?? [] as $propertyName => $deprecatedMessage) {
+			$propertyAnnotation = $class->getStaticProperty($propertyName);
 			$this->assertSame($deprecated, $propertyAnnotation->isDeprecated()->yes());
 			$this->assertSame($deprecatedMessage, $propertyAnnotation->getDeprecatedDescription());
 		}
diff --git a/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php
index d7af0d248f..749f719194 100644
--- a/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php
+++ b/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php
@@ -31,6 +31,8 @@ public function dataInternalAnnotations(): array
 					],
 					'property' => [
 						'foo',
+					],
+					'staticProperty' => [
 						'staticFoo',
 					],
 				],
@@ -48,6 +50,8 @@ public function dataInternalAnnotations(): array
 					],
 					'property' => [
 						'internalFoo',
+					],
+					'staticProperty' => [
 						'internalStaticFoo',
 					],
 				],
@@ -88,6 +92,8 @@ public function dataInternalAnnotations(): array
 					],
 					'property' => [
 						'foo',
+					],
+					'staticProperty' => [
 						'staticFoo',
 					],
 				],
@@ -102,6 +108,8 @@ public function dataInternalAnnotations(): array
 					],
 					'property' => [
 						'internalFoo',
+					],
+					'staticProperty' => [
 						'internalStaticFoo',
 					],
 				],
@@ -132,7 +140,12 @@ public function testInternalAnnotations(bool $internal, string $className, array
 		}
 
 		foreach ($internalAnnotations['property'] ?? [] as $propertyName) {
-			$propertyAnnotation = $class->getProperty($propertyName, $scope);
+			$propertyAnnotation = $class->getInstanceProperty($propertyName, $scope);
+			$this->assertSame($internal, $propertyAnnotation->isInternal()->yes());
+		}
+
+		foreach ($internalAnnotations['staticProperty'] ?? [] as $propertyName) {
+			$propertyAnnotation = $class->getStaticProperty($propertyName);
 			$this->assertSame($internal, $propertyAnnotation->isInternal()->yes());
 		}
 
diff --git a/tests/PHPStan/Reflection/Deprecation/DeprecationProviderTest.php b/tests/PHPStan/Reflection/Deprecation/DeprecationProviderTest.php
index c52796550d..ad5d59bab4 100644
--- a/tests/PHPStan/Reflection/Deprecation/DeprecationProviderTest.php
+++ b/tests/PHPStan/Reflection/Deprecation/DeprecationProviderTest.php
@@ -110,29 +110,29 @@ public function testCustomDeprecations(): void
 		self::assertSame('attribute', $doubleDeprecatedClassOnlyAttributeMessage->getConstant('FOO')->getDeprecatedDescription());
 
 		// properties
-		self::assertFalse($notDeprecatedClass->getProperty('foo', $scopeForNotDeprecatedClass)->isDeprecated()->yes());
-		self::assertNull($notDeprecatedClass->getProperty('foo', $scopeForNotDeprecatedClass)->getDeprecatedDescription());
+		self::assertFalse($notDeprecatedClass->getInstanceProperty('foo', $scopeForNotDeprecatedClass)->isDeprecated()->yes());
+		self::assertNull($notDeprecatedClass->getInstanceProperty('foo', $scopeForNotDeprecatedClass)->getDeprecatedDescription());
 
-		self::assertTrue($attributeDeprecatedClass->getProperty('foo', $scopeForDeprecatedClass)->isDeprecated()->yes());
-		self::assertNull($attributeDeprecatedClass->getProperty('foo', $scopeForDeprecatedClass)->getDeprecatedDescription());
+		self::assertTrue($attributeDeprecatedClass->getInstanceProperty('foo', $scopeForDeprecatedClass)->isDeprecated()->yes());
+		self::assertNull($attributeDeprecatedClass->getInstanceProperty('foo', $scopeForDeprecatedClass)->getDeprecatedDescription());
 
-		self::assertTrue($phpDocDeprecatedClass->getProperty('foo', $scopeForPhpDocDeprecatedClass)->isDeprecated()->yes());
-		self::assertNull($phpDocDeprecatedClass->getProperty('foo', $scopeForPhpDocDeprecatedClass)->getDeprecatedDescription());
+		self::assertTrue($phpDocDeprecatedClass->getInstanceProperty('foo', $scopeForPhpDocDeprecatedClass)->isDeprecated()->yes());
+		self::assertNull($phpDocDeprecatedClass->getInstanceProperty('foo', $scopeForPhpDocDeprecatedClass)->getDeprecatedDescription());
 
-		self::assertTrue($phpDocDeprecatedClassWithMessages->getProperty('foo', $scopeForPhpDocDeprecatedClassWithMessages)->isDeprecated()->yes());
-		self::assertSame('phpdoc', $phpDocDeprecatedClassWithMessages->getProperty('foo', $scopeForPhpDocDeprecatedClassWithMessages)->getDeprecatedDescription());
+		self::assertTrue($phpDocDeprecatedClassWithMessages->getInstanceProperty('foo', $scopeForPhpDocDeprecatedClassWithMessages)->isDeprecated()->yes());
+		self::assertSame('phpdoc', $phpDocDeprecatedClassWithMessages->getInstanceProperty('foo', $scopeForPhpDocDeprecatedClassWithMessages)->getDeprecatedDescription());
 
-		self::assertTrue($attributeDeprecatedClassWithMessages->getProperty('foo', $scopeForAttributeDeprecatedClassWithMessages)->isDeprecated()->yes());
-		self::assertSame('attribute', $attributeDeprecatedClassWithMessages->getProperty('foo', $scopeForAttributeDeprecatedClassWithMessages)->getDeprecatedDescription());
+		self::assertTrue($attributeDeprecatedClassWithMessages->getInstanceProperty('foo', $scopeForAttributeDeprecatedClassWithMessages)->isDeprecated()->yes());
+		self::assertSame('attribute', $attributeDeprecatedClassWithMessages->getInstanceProperty('foo', $scopeForAttributeDeprecatedClassWithMessages)->getDeprecatedDescription());
 
-		self::assertTrue($doubleDeprecatedClass->getProperty('foo', $scopeForDoubleDeprecatedClass)->isDeprecated()->yes());
-		self::assertSame('attribute', $doubleDeprecatedClass->getProperty('foo', $scopeForDoubleDeprecatedClass)->getDeprecatedDescription());
+		self::assertTrue($doubleDeprecatedClass->getInstanceProperty('foo', $scopeForDoubleDeprecatedClass)->isDeprecated()->yes());
+		self::assertSame('attribute', $doubleDeprecatedClass->getInstanceProperty('foo', $scopeForDoubleDeprecatedClass)->getDeprecatedDescription());
 
-		self::assertTrue($doubleDeprecatedClassOnlyPhpDocMessage->getProperty('foo', $scopeForDoubleDeprecatedClassOnlyNativeMessage)->isDeprecated()->yes());
-		self::assertNull($doubleDeprecatedClassOnlyPhpDocMessage->getProperty('foo', $scopeForDoubleDeprecatedClassOnlyNativeMessage)->getDeprecatedDescription());
+		self::assertTrue($doubleDeprecatedClassOnlyPhpDocMessage->getInstanceProperty('foo', $scopeForDoubleDeprecatedClassOnlyNativeMessage)->isDeprecated()->yes());
+		self::assertNull($doubleDeprecatedClassOnlyPhpDocMessage->getInstanceProperty('foo', $scopeForDoubleDeprecatedClassOnlyNativeMessage)->getDeprecatedDescription());
 
-		self::assertTrue($doubleDeprecatedClassOnlyAttributeMessage->getProperty('foo', $scopeForDoubleDeprecatedClassOnlyCustomMessage)->isDeprecated()->yes());
-		self::assertSame('attribute', $doubleDeprecatedClassOnlyAttributeMessage->getProperty('foo', $scopeForDoubleDeprecatedClassOnlyCustomMessage)->getDeprecatedDescription());
+		self::assertTrue($doubleDeprecatedClassOnlyAttributeMessage->getInstanceProperty('foo', $scopeForDoubleDeprecatedClassOnlyCustomMessage)->isDeprecated()->yes());
+		self::assertSame('attribute', $doubleDeprecatedClassOnlyAttributeMessage->getInstanceProperty('foo', $scopeForDoubleDeprecatedClassOnlyCustomMessage)->getDeprecatedDescription());
 
 		// methods
 		self::assertFalse($notDeprecatedClass->getMethod('foo', $scopeForNotDeprecatedClass)->isDeprecated()->yes());
diff --git a/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php b/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php
index e14d95f33e..8ade9becb8 100644
--- a/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php
+++ b/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php
@@ -42,22 +42,22 @@ public function testRuleOutOfPhpStan(): void
 			],
 			[
 				'Implementing PHPStan\Reflection\ReflectionProvider is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.',
-				333,
+				363,
 				$tip,
 			],
 			[
 				'Implementing PHPStan\Analyser\Scope is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.',
-				338,
+				368,
 				$tip,
 			],
 			[
 				'Implementing PHPStan\Reflection\FunctionReflection is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.',
-				343,
+				373,
 				$tip,
 			],
 			[
 				'Implementing PHPStan\Reflection\ExtendedMethodReflection is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.',
-				347,
+				377,
 				$tip,
 			],
 		]);
diff --git a/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php b/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php
index 6ded7325fb..44204e0f2c 100644
--- a/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php
+++ b/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php
@@ -98,6 +98,36 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
 		// TODO: Implement getUnresolvedPropertyPrototype() method.
 	}
 
+	public function hasInstanceProperty(string $propertyName): \PHPStan\TrinaryLogic
+	{
+		// TODO: Implement hasInstanceProperty() method.
+	}
+
+	public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): \PHPStan\Reflection\PropertyReflection
+	{
+		// TODO: Implement getInstanceProperty() method.
+	}
+
+	public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): \PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection
+	{
+		// TODO: Implement getUnresolvedInstancePropertyPrototype() method.
+	}
+
+	public function hasStaticProperty(string $propertyName): \PHPStan\TrinaryLogic
+	{
+		// TODO: Implement hasStaticProperty() method.
+	}
+
+	public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): \PHPStan\Reflection\PropertyReflection
+	{
+		// TODO: Implement getStaticProperty() method.
+	}
+
+	public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): \PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection
+	{
+		// TODO: Implement getUnresolvedStaticPropertyPrototype() method.
+	}
+
 	public function canCallMethods(): \PHPStan\TrinaryLogic
 	{
 		// TODO: Implement canCallMethods() method.
diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php
index 88834051bb..9bb5e645f7 100644
--- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php
+++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php
@@ -310,4 +310,9 @@ public function testBug8333(): void
 		]);
 	}
 
+	public function testBug12775(): void
+	{
+		$this->analyse([__DIR__ . '/data/bug-12775.php'], []);
+	}
+
 }
diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php
index 8d050e7636..160ff6af82 100644
--- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php
+++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php
@@ -43,10 +43,6 @@ public function testTypesAssignedToProperties(): void
 				'Static property PropertiesAssignedTypes\Foo::$staticStringProperty (string) does not accept int.',
 				37,
 			],
-			[
-				'Property PropertiesAssignedTypes\Ipsum::$parentStringProperty (string) does not accept int.',
-				39,
-			],
 			[
 				'Property PropertiesAssignedTypes\Foo::$unionPropertySelf (array<PropertiesAssignedTypes\Foo>|(iterable<PropertiesAssignedTypes\Foo>&PropertiesAssignedTypes\Collection)) does not accept PropertiesAssignedTypes\Foo.',
 				44,
diff --git a/tests/PHPStan/Rules/Properties/data/bug-12775.php b/tests/PHPStan/Rules/Properties/data/bug-12775.php
new file mode 100644
index 0000000000..bdade366f3
--- /dev/null
+++ b/tests/PHPStan/Rules/Properties/data/bug-12775.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Bug12775;
+
+/**
+ * @property string $comment
+ */
+class Foo {
+	public static string $comment = 'foo';
+
+	public function __get(string $name): mixed {
+		return 'bar';
+	}
+}
+
+$foo = new Foo;
+var_dump(Foo::$comment);
diff --git a/tests/PHPStan/Type/BenevolentUnionTypeTest.php b/tests/PHPStan/Type/BenevolentUnionTypeTest.php
index 18a37f8be0..cb2f6c7ae3 100644
--- a/tests/PHPStan/Type/BenevolentUnionTypeTest.php
+++ b/tests/PHPStan/Type/BenevolentUnionTypeTest.php
@@ -48,7 +48,7 @@ public function testCanAccessProperties(BenevolentUnionType $type, TrinaryLogic
 		);
 	}
 
-	public function dataHasProperty(): Iterator
+	public function dataHasInstanceProperty(): Iterator
 	{
 		yield [
 			new BenevolentUnionType([
@@ -75,10 +75,10 @@ public function dataHasProperty(): Iterator
 		];
 	}
 
-	/** @dataProvider dataHasProperty */
-	public function testHasProperty(BenevolentUnionType $type, string $propertyName, TrinaryLogic $expectedResult): void
+	/** @dataProvider dataHasInstanceProperty */
+	public function testHasInstanceProperty(BenevolentUnionType $type, string $propertyName, TrinaryLogic $expectedResult): void
 	{
-		$actualResult = $type->hasProperty($propertyName);
+		$actualResult = $type->hasInstanceProperty($propertyName);
 		$this->assertSame(
 			$expectedResult->describe(),
 			$actualResult->describe(),