Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Difference between Instance and Static properties #3906

Draft
wants to merge 12 commits into
base: 2.1.x
Choose a base branch
from
40 changes: 37 additions & 3 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -4237,7 +4237,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;
}
Expand Down Expand Up @@ -6062,7 +6062,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) {
Expand All @@ -6075,12 +6078,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;
}
Expand Down
8 changes: 4 additions & 4 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -3021,7 +3021,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)) {
Expand Down Expand Up @@ -5563,8 +5563,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()) {
Expand Down Expand Up @@ -5635,7 +5635,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()) {
Expand Down
5 changes: 5 additions & 0 deletions src/Analyser/Scope.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions src/Dependency/DependencyResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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(), $scope);
$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);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Node/ClassPropertiesNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ public function getUninitializedProperties(
continue;
}

$propertyReflection = $usageScope->getPropertyReflection($fetchedOnType, $propertyName);
$propertyReflection = $usageScope->getInstancePropertyReflection($fetchedOnType, $propertyName);
if ($propertyReflection === null) {
continue;
}
Expand Down
163 changes: 162 additions & 1 deletion src/Reflection/ClassReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ final class ClassReflection
/** @var ExtendedPropertyReflection[] */
private array $properties = [];

/** @var ExtendedPropertyReflection[] */
private array $instanceProperties = [];

/** @var ExtendedPropertyReflection[] */
private array $staticProperties = [];

/** @var RealClassClassConstantReflection[] */
private array $constants = [];

Expand Down Expand Up @@ -147,6 +153,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
Expand Down Expand Up @@ -460,6 +472,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)) {
Expand All @@ -479,13 +494,61 @@ 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;
}

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)) {
Expand Down Expand Up @@ -630,6 +693,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;
Expand All @@ -640,6 +717,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()) {
Expand Down Expand Up @@ -669,6 +747,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);
Expand All @@ -683,9 +770,83 @@ 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, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
{
$key = $propertyName;
if ($scope->isInClass()) {
$key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey());
}

if (!isset($this->staticProperties[$key])) {
if ($this->getPhpExtension()->hasStaticProperty($this, $propertyName)) {
$property = $this->wrapExtendedProperty($this->getPhpExtension()->getStaticProperty($this, $propertyName));
if ($scope->canReadProperty($property)) {
return $this->staticProperties[$key] = $property;
}
$this->staticProperties[$key] = $property;
}
}

if (!isset($this->staticProperties[$key])) {
if ($this->requireExtendsPropertiesClassReflectionExtension->hasStaticProperty($this, $propertyName)) {
$property = $this->requireExtendsPropertiesClassReflectionExtension->getStaticProperty($this, $propertyName);
$this->staticProperties[$key] = $property;
}
}

if (!isset($this->staticProperties[$key])) {
throw new MissingPropertyFromReflectionException($this->getName(), $propertyName);
}

return $this->staticProperties[$key];
}

public function hasNativeProperty(string $propertyName): bool
{
return $this->getPhpExtension()->hasProperty($this, $propertyName);
return $this->getPhpExtension()->hasNativeProperty($this, $propertyName);
}

public function getNativeProperty(string $propertyName): PhpPropertyReflection
Expand Down
4 changes: 2 additions & 2 deletions src/Reflection/InitializerExprTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading
Loading