|
3 | 3 | namespace PHPStan\Analyser\ExprHandler; |
4 | 4 |
|
5 | 5 | use PhpParser\Node\Expr; |
| 6 | +use PhpParser\Node\Expr\BinaryOp\Identical; |
6 | 7 | use PhpParser\Node\Expr\StaticPropertyFetch; |
| 8 | +use PhpParser\Node\Name; |
| 9 | +use PhpParser\Node\Scalar\String_; |
7 | 10 | use PhpParser\Node\Stmt; |
| 11 | +use PhpParser\Node\VarLikeIdentifier; |
8 | 12 | use PHPStan\Analyser\ExpressionContext; |
9 | 13 | use PHPStan\Analyser\ExpressionResult; |
10 | 14 | use PHPStan\Analyser\ExpressionResultStorage; |
11 | 15 | use PHPStan\Analyser\ExprHandler; |
| 16 | +use PHPStan\Analyser\ExprHandler\Helper\NullsafeShortCircuitingHelper; |
12 | 17 | use PHPStan\Analyser\ImpurePoint; |
13 | 18 | use PHPStan\Analyser\MutatingScope; |
14 | 19 | use PHPStan\Analyser\NodeScopeResolver; |
15 | 20 | use PHPStan\DependencyInjection\AutowiredService; |
| 21 | +use PHPStan\Rules\Properties\PropertyReflectionFinder; |
| 22 | +use PHPStan\Type\ErrorType; |
| 23 | +use PHPStan\Type\MixedType; |
| 24 | +use PHPStan\Type\Type; |
| 25 | +use PHPStan\Type\TypeCombinator; |
| 26 | +use function array_map; |
16 | 27 | use function array_merge; |
| 28 | +use function count; |
17 | 29 |
|
18 | 30 | /** |
19 | 31 | * @implements ExprHandler<StaticPropertyFetch> |
|
22 | 34 | final class StaticPropertyFetchHandler implements ExprHandler |
23 | 35 | { |
24 | 36 |
|
| 37 | + public function __construct( |
| 38 | + private PropertyReflectionFinder $propertyReflectionFinder, |
| 39 | + ) |
| 40 | + { |
| 41 | + } |
| 42 | + |
25 | 43 | public function supports(Expr $expr): bool |
26 | 44 | { |
27 | 45 | return $expr instanceof StaticPropertyFetch; |
@@ -69,4 +87,77 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex |
69 | 87 | ); |
70 | 88 | } |
71 | 89 |
|
| 90 | + /** |
| 91 | + * @param StaticPropertyFetch $expr |
| 92 | + */ |
| 93 | + public function resolveType(MutatingScope $scope, Expr $expr): Type |
| 94 | + { |
| 95 | + if ($expr->name instanceof VarLikeIdentifier) { |
| 96 | + if ($scope->nativeTypesPromoted) { |
| 97 | + $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($expr, $scope); |
| 98 | + if ($propertyReflection === null) { |
| 99 | + return new ErrorType(); |
| 100 | + } |
| 101 | + if (!$propertyReflection->hasNativeType()) { |
| 102 | + return new MixedType(); |
| 103 | + } |
| 104 | + |
| 105 | + $nativeType = $propertyReflection->getNativeType(); |
| 106 | + |
| 107 | + if ($expr->class instanceof Expr) { |
| 108 | + return NullsafeShortCircuitingHelper::getType($scope, $expr->class, $nativeType); |
| 109 | + } |
| 110 | + |
| 111 | + return $nativeType; |
| 112 | + } |
| 113 | + |
| 114 | + if ($expr->class instanceof Name) { |
| 115 | + $staticPropertyFetchedOnType = $scope->resolveTypeByName($expr->class); |
| 116 | + } else { |
| 117 | + $staticPropertyFetchedOnType = TypeCombinator::removeNull($scope->getType($expr->class))->getObjectTypeOrClassStringObjectType(); |
| 118 | + } |
| 119 | + |
| 120 | + $fetchType = $this->propertyFetchType( |
| 121 | + $scope, |
| 122 | + $staticPropertyFetchedOnType, |
| 123 | + $expr->name->toString(), |
| 124 | + $expr, |
| 125 | + ); |
| 126 | + if ($fetchType === null) { |
| 127 | + $fetchType = new ErrorType(); |
| 128 | + } |
| 129 | + |
| 130 | + if ($expr->class instanceof Expr) { |
| 131 | + return NullsafeShortCircuitingHelper::getType($scope, $expr->class, $fetchType); |
| 132 | + } |
| 133 | + |
| 134 | + return $fetchType; |
| 135 | + } |
| 136 | + |
| 137 | + $nameType = $scope->getType($expr->name); |
| 138 | + if (count($nameType->getConstantStrings()) > 0) { |
| 139 | + return TypeCombinator::union( |
| 140 | + ...array_map(static fn ($constantString) => $constantString->getValue() === '' ? new ErrorType() : $scope |
| 141 | + ->filterByTruthyValue(new Identical($expr->name, new String_($constantString->getValue()))) |
| 142 | + ->getType(new Expr\StaticPropertyFetch($expr->class, new VarLikeIdentifier($constantString->getValue()))), $nameType->getConstantStrings()), |
| 143 | + ); |
| 144 | + } |
| 145 | + |
| 146 | + return new MixedType(); |
| 147 | + } |
| 148 | + |
| 149 | + private function propertyFetchType(MutatingScope $scope, Type $fetchedOnType, string $propertyName, StaticPropertyFetch $propertyFetch): ?Type |
| 150 | + { |
| 151 | + $propertyReflection = $scope->getStaticPropertyReflection($fetchedOnType, $propertyName); |
| 152 | + if ($propertyReflection === null) { |
| 153 | + return null; |
| 154 | + } |
| 155 | + |
| 156 | + if ($scope->isInExpressionAssign($propertyFetch)) { |
| 157 | + return $propertyReflection->getWritableType(); |
| 158 | + } |
| 159 | + |
| 160 | + return $propertyReflection->getReadableType(); |
| 161 | + } |
| 162 | + |
72 | 163 | } |
0 commit comments