| 1: | <?php declare(strict_types = 1); | 
| 2: |  | 
| 3: | namespace PHPStan\Node; | 
| 4: |  | 
| 5: | use PhpParser\Node; | 
| 6: | use PhpParser\Node\Expr\Array_; | 
| 7: | use PhpParser\Node\Expr\PropertyFetch; | 
| 8: | use PhpParser\Node\Identifier; | 
| 9: | use PhpParser\Node\Name; | 
| 10: | use PhpParser\Node\Stmt\Class_; | 
| 11: | use PhpParser\Node\Stmt\ClassLike; | 
| 12: | use PhpParser\NodeAbstract; | 
| 13: | use PHPStan\Analyser\Scope; | 
| 14: | use PHPStan\Node\Method\MethodCall; | 
| 15: | use PHPStan\Node\Property\PropertyRead; | 
| 16: | use PHPStan\Node\Property\PropertyWrite; | 
| 17: | use PHPStan\Reflection\MethodReflection; | 
| 18: | use PHPStan\Rules\Properties\ReadWritePropertiesExtension; | 
| 19: | use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; | 
| 20: | use PHPStan\ShouldNotHappenException; | 
| 21: | use PHPStan\Type\MixedType; | 
| 22: | use PHPStan\Type\ObjectType; | 
| 23: | use function array_key_exists; | 
| 24: | use function array_keys; | 
| 25: | use function count; | 
| 26: | use function in_array; | 
| 27: |  | 
| 28: |  | 
| 29: | class ClassPropertiesNode extends NodeAbstract implements VirtualNode | 
| 30: | { | 
| 31: |  | 
| 32: |  | 
| 33: |  | 
| 34: |  | 
| 35: |  | 
| 36: |  | 
| 37: | public function __construct( | 
| 38: | private ClassLike $class, | 
| 39: | private ReadWritePropertiesExtensionProvider $readWritePropertiesExtensionProvider, | 
| 40: | private array $properties, | 
| 41: | private array $propertyUsages, | 
| 42: | private array $methodCalls, | 
| 43: | ) | 
| 44: | { | 
| 45: | parent::__construct($class->getAttributes()); | 
| 46: | } | 
| 47: |  | 
| 48: | public function getClass(): ClassLike | 
| 49: | { | 
| 50: | return $this->class; | 
| 51: | } | 
| 52: |  | 
| 53: |  | 
| 54: |  | 
| 55: |  | 
| 56: | public function getProperties(): array | 
| 57: | { | 
| 58: | return $this->properties; | 
| 59: | } | 
| 60: |  | 
| 61: |  | 
| 62: |  | 
| 63: |  | 
| 64: | public function getPropertyUsages(): array | 
| 65: | { | 
| 66: | return $this->propertyUsages; | 
| 67: | } | 
| 68: |  | 
| 69: | public function getType(): string | 
| 70: | { | 
| 71: | return 'PHPStan_Node_ClassPropertiesNode'; | 
| 72: | } | 
| 73: |  | 
| 74: |  | 
| 75: |  | 
| 76: |  | 
| 77: | public function getSubNodeNames(): array | 
| 78: | { | 
| 79: | return []; | 
| 80: | } | 
| 81: |  | 
| 82: |  | 
| 83: |  | 
| 84: |  | 
| 85: |  | 
| 86: |  | 
| 87: | public function getUninitializedProperties( | 
| 88: | Scope $scope, | 
| 89: | array $constructors, | 
| 90: | ?array $extensions = null, | 
| 91: | ): array | 
| 92: | { | 
| 93: | if (!$this->getClass() instanceof Class_) { | 
| 94: | return [[], [], []]; | 
| 95: | } | 
| 96: | if (!$scope->isInClass()) { | 
| 97: | throw new ShouldNotHappenException(); | 
| 98: | } | 
| 99: | $classReflection = $scope->getClassReflection(); | 
| 100: |  | 
| 101: | $properties = []; | 
| 102: | foreach ($this->getProperties() as $property) { | 
| 103: | if ($property->isStatic()) { | 
| 104: | continue; | 
| 105: | } | 
| 106: | if ($property->getNativeType() === null) { | 
| 107: | continue; | 
| 108: | } | 
| 109: | if ($property->getDefault() !== null) { | 
| 110: | continue; | 
| 111: | } | 
| 112: | $properties[$property->getName()] = $property; | 
| 113: | } | 
| 114: |  | 
| 115: | if ($extensions === null) { | 
| 116: | $extensions = $this->readWritePropertiesExtensionProvider->getExtensions(); | 
| 117: | } | 
| 118: |  | 
| 119: | foreach (array_keys($properties) as $name) { | 
| 120: | foreach ($extensions as $extension) { | 
| 121: | if (!$classReflection->hasNativeProperty($name)) { | 
| 122: | continue; | 
| 123: | } | 
| 124: | $propertyReflection = $classReflection->getNativeProperty($name); | 
| 125: | if (!$extension->isInitialized($propertyReflection, $name)) { | 
| 126: | continue; | 
| 127: | } | 
| 128: | unset($properties[$name]); | 
| 129: | break; | 
| 130: | } | 
| 131: | } | 
| 132: |  | 
| 133: | if ($constructors === []) { | 
| 134: | return [$properties, [], []]; | 
| 135: | } | 
| 136: | $classType = new ObjectType($scope->getClassReflection()->getName()); | 
| 137: | $methodsCalledFromConstructor = $this->getMethodsCalledFromConstructor($classType, $this->methodCalls, $constructors); | 
| 138: | $prematureAccess = []; | 
| 139: | $additionalAssigns = []; | 
| 140: | $originalProperties = $properties; | 
| 141: | foreach ($this->getPropertyUsages() as $usage) { | 
| 142: | $fetch = $usage->getFetch(); | 
| 143: | if (!$fetch instanceof PropertyFetch) { | 
| 144: | continue; | 
| 145: | } | 
| 146: | $usageScope = $usage->getScope(); | 
| 147: | if ($usageScope->getFunction() === null) { | 
| 148: | continue; | 
| 149: | } | 
| 150: | $function = $usageScope->getFunction(); | 
| 151: | if (!$function instanceof MethodReflection) { | 
| 152: | continue; | 
| 153: | } | 
| 154: | if ($function->getDeclaringClass()->getName() !== $classReflection->getName()) { | 
| 155: | continue; | 
| 156: | } | 
| 157: | if (!in_array($function->getName(), $methodsCalledFromConstructor, true)) { | 
| 158: | continue; | 
| 159: | } | 
| 160: |  | 
| 161: | if (!$fetch->name instanceof Identifier) { | 
| 162: | continue; | 
| 163: | } | 
| 164: | $propertyName = $fetch->name->toString(); | 
| 165: | $fetchedOnType = $usageScope->getType($fetch->var); | 
| 166: | if ($classType->isSuperTypeOf($fetchedOnType)->no()) { | 
| 167: | continue; | 
| 168: | } | 
| 169: | if ($fetchedOnType instanceof MixedType) { | 
| 170: | continue; | 
| 171: | } | 
| 172: |  | 
| 173: | if ($usage instanceof PropertyWrite) { | 
| 174: | if (array_key_exists($propertyName, $properties)) { | 
| 175: | unset($properties[$propertyName]); | 
| 176: | } elseif (array_key_exists($propertyName, $originalProperties)) { | 
| 177: | $additionalAssigns[] = [ | 
| 178: | $propertyName, | 
| 179: | $fetch->getLine(), | 
| 180: | $originalProperties[$propertyName], | 
| 181: | ]; | 
| 182: | } | 
| 183: | } elseif (array_key_exists($propertyName, $properties)) { | 
| 184: | $prematureAccess[] = [ | 
| 185: | $propertyName, | 
| 186: | $fetch->getLine(), | 
| 187: | $properties[$propertyName], | 
| 188: | ]; | 
| 189: | } | 
| 190: | } | 
| 191: |  | 
| 192: | return [ | 
| 193: | $properties, | 
| 194: | $prematureAccess, | 
| 195: | $additionalAssigns, | 
| 196: | ]; | 
| 197: | } | 
| 198: |  | 
| 199: |  | 
| 200: |  | 
| 201: |  | 
| 202: |  | 
| 203: |  | 
| 204: | private function getMethodsCalledFromConstructor( | 
| 205: | ObjectType $classType, | 
| 206: | array $methodCalls, | 
| 207: | array $methods, | 
| 208: | ): array | 
| 209: | { | 
| 210: | $originalCount = count($methods); | 
| 211: | foreach ($methodCalls as $methodCall) { | 
| 212: | $methodCallNode = $methodCall->getNode(); | 
| 213: | if ($methodCallNode instanceof Array_) { | 
| 214: | continue; | 
| 215: | } | 
| 216: | if (!$methodCallNode->name instanceof Identifier) { | 
| 217: | continue; | 
| 218: | } | 
| 219: | $callScope = $methodCall->getScope(); | 
| 220: | if ($methodCallNode instanceof Node\Expr\MethodCall) { | 
| 221: | $calledOnType = $callScope->getType($methodCallNode->var); | 
| 222: | } else { | 
| 223: | if (!$methodCallNode->class instanceof Name) { | 
| 224: | continue; | 
| 225: | } | 
| 226: |  | 
| 227: | $calledOnType = $callScope->resolveTypeByName($methodCallNode->class); | 
| 228: | } | 
| 229: | if ($classType->isSuperTypeOf($calledOnType)->no()) { | 
| 230: | continue; | 
| 231: | } | 
| 232: | if ($calledOnType instanceof MixedType) { | 
| 233: | continue; | 
| 234: | } | 
| 235: | $methodName = $methodCallNode->name->toString(); | 
| 236: | if (in_array($methodName, $methods, true)) { | 
| 237: | continue; | 
| 238: | } | 
| 239: | $inMethod = $callScope->getFunction(); | 
| 240: | if (!$inMethod instanceof MethodReflection) { | 
| 241: | continue; | 
| 242: | } | 
| 243: | if (!in_array($inMethod->getName(), $methods, true)) { | 
| 244: | continue; | 
| 245: | } | 
| 246: | $methods[] = $methodName; | 
| 247: | } | 
| 248: |  | 
| 249: | if ($originalCount === count($methods)) { | 
| 250: | return $methods; | 
| 251: | } | 
| 252: |  | 
| 253: | return $this->getMethodsCalledFromConstructor($classType, $methodCalls, $methods); | 
| 254: | } | 
| 255: |  | 
| 256: | } | 
| 257: |  |