Skip to content

Commit 1e3ff2c

Browse files
committed
Do not use instanceof *Type
1 parent 7024c2f commit 1e3ff2c

File tree

1 file changed

+62
-59
lines changed

1 file changed

+62
-59
lines changed

src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

Lines changed: 62 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,18 @@
3535
use PHPStan\Analyser\TypeSpecifierAwareExtension;
3636
use PHPStan\Analyser\TypeSpecifierContext;
3737
use PHPStan\Reflection\MethodReflection;
38+
use PHPStan\Reflection\ReflectionProvider;
3839
use PHPStan\ShouldNotHappenException;
3940
use PHPStan\Type\ArrayType;
40-
use PHPStan\Type\Constant\ConstantArrayType;
4141
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
4242
use PHPStan\Type\Constant\ConstantBooleanType;
43-
use PHPStan\Type\Constant\ConstantStringType;
4443
use PHPStan\Type\IterableType;
4544
use PHPStan\Type\MixedType;
4645
use PHPStan\Type\NeverType;
47-
use PHPStan\Type\ObjectType;
4846
use PHPStan\Type\StaticMethodTypeSpecifyingExtension;
4947
use PHPStan\Type\StringType;
5048
use PHPStan\Type\Type;
5149
use PHPStan\Type\TypeCombinator;
52-
use PHPStan\Type\TypeWithClassName;
5350
use ReflectionObject;
5451
use Traversable;
5552
use function array_filter;
@@ -66,11 +63,19 @@ class AssertTypeSpecifyingExtension implements StaticMethodTypeSpecifyingExtensi
6663
{
6764

6865
/** @var Closure[] */
69-
private static $resolvers;
66+
private $resolvers;
67+
68+
/** @var ReflectionProvider */
69+
private $reflectionProvider;
7070

7171
/** @var TypeSpecifier */
7272
private $typeSpecifier;
7373

74+
public function __construct(ReflectionProvider $reflectionProvider)
75+
{
76+
$this->reflectionProvider = $reflectionProvider;
77+
}
78+
7479
public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
7580
{
7681
$this->typeSpecifier = $typeSpecifier;
@@ -98,7 +103,7 @@ public function isStaticMethodSupported(
98103
}
99104

100105
$trimmedName = self::trimName($staticMethodReflection->getName());
101-
$resolvers = self::getExpressionResolvers();
106+
$resolvers = $this->getExpressionResolvers();
102107

103108
if (!array_key_exists($trimmedName, $resolvers)) {
104109
return false;
@@ -176,14 +181,14 @@ static function (Type $type) {
176181
* @param Arg[] $args
177182
* @return array{?Expr, ?Expr}
178183
*/
179-
private static function createExpression(
184+
private function createExpression(
180185
Scope $scope,
181186
string $name,
182187
array $args
183188
): array
184189
{
185190
$trimmedName = self::trimName($name);
186-
$resolvers = self::getExpressionResolvers();
191+
$resolvers = $this->getExpressionResolvers();
187192
$resolver = $resolvers[$trimmedName];
188193

189194
$resolverResult = $resolver($scope, ...$args);
@@ -214,10 +219,10 @@ private static function createExpression(
214219
/**
215220
* @return array<string, callable(Scope, Arg...): (Expr|array{?Expr, ?Expr}|null)>
216221
*/
217-
private static function getExpressionResolvers(): array
222+
private function getExpressionResolvers(): array
218223
{
219-
if (self::$resolvers === null) {
220-
self::$resolvers = [
224+
if ($this->resolvers === null) {
225+
$this->resolvers = [
221226
'integer' => static function (Scope $scope, Arg $value): Expr {
222227
return new FuncCall(
223228
new Name('is_int'),
@@ -328,8 +333,8 @@ private static function getExpressionResolvers(): array
328333
[$value]
329334
);
330335
},
331-
'isTraversable' => static function (Scope $scope, Arg $value): Expr {
332-
return self::$resolvers['isIterable']($scope, $value);
336+
'isTraversable' => function (Scope $scope, Arg $value): Expr {
337+
return $this->resolvers['isIterable']($scope, $value);
333338
},
334339
'isIterable' => static function (Scope $scope, Arg $expr): Expr {
335340
return new BooleanOr(
@@ -358,9 +363,9 @@ private static function getExpressionResolvers(): array
358363
)
359364
);
360365
},
361-
'isNonEmptyList' => static function (Scope $scope, Arg $expr): Expr {
366+
'isNonEmptyList' => function (Scope $scope, Arg $expr): Expr {
362367
return new BooleanAnd(
363-
self::$resolvers['isList']($scope, $expr),
368+
$this->resolvers['isList']($scope, $expr),
364369
new NotIdentical(
365370
$expr->value,
366371
new Array_()
@@ -382,9 +387,9 @@ private static function getExpressionResolvers(): array
382387
)
383388
);
384389
},
385-
'isNonEmptyMap' => static function (Scope $scope, Arg $expr): Expr {
390+
'isNonEmptyMap' => function (Scope $scope, Arg $expr): Expr {
386391
return new BooleanAnd(
387-
self::$resolvers['isMap']($scope, $expr),
392+
$this->resolvers['isMap']($scope, $expr),
388393
new NotIdentical(
389394
$expr->value,
390395
new Array_()
@@ -405,24 +410,22 @@ private static function getExpressionResolvers(): array
405410
},
406411
'isInstanceOf' => static function (Scope $scope, Arg $expr, Arg $class): ?Expr {
407412
$classType = $scope->getType($class->value);
408-
if ($classType instanceof ConstantStringType) {
409-
$className = new Name($classType->getValue());
410-
} elseif ($classType instanceof TypeWithClassName) {
411-
$className = new Name($classType->getClassName());
412-
} else {
413+
$classNameType = $classType->getObjectTypeOrClassStringObjectType();
414+
$classNames = $classNameType->getObjectClassNames();
415+
if (count($classNames) !== 1) {
413416
return null;
414417
}
415418

416419
return new Instanceof_(
417420
$expr->value,
418-
$className
421+
new Name($classNames[0])
419422
);
420423
},
421-
'isInstanceOfAny' => static function (Scope $scope, Arg $expr, Arg $classes): ?Expr {
422-
return self::buildAnyOfExpr($scope, $expr, $classes, self::$resolvers['isInstanceOf']);
424+
'isInstanceOfAny' => function (Scope $scope, Arg $expr, Arg $classes): ?Expr {
425+
return self::buildAnyOfExpr($scope, $expr, $classes, $this->resolvers['isInstanceOf']);
423426
},
424-
'notInstanceOf' => static function (Scope $scope, Arg $expr, Arg $class): ?Expr {
425-
$expr = self::$resolvers['isInstanceOf']($scope, $expr, $class);
427+
'notInstanceOf' => function (Scope $scope, Arg $expr, Arg $class): ?Expr {
428+
$expr = $this->resolvers['isInstanceOf']($scope, $expr, $class);
426429
if ($expr === null) {
427430
return null;
428431
}
@@ -438,37 +441,39 @@ private static function getExpressionResolvers(): array
438441
[$expr, $class, new Arg(new ConstFetch(new Name($allowString ? 'true' : 'false')))]
439442
);
440443
},
441-
'isAnyOf' => static function (Scope $scope, Arg $value, Arg $classes): ?Expr {
442-
return self::buildAnyOfExpr($scope, $value, $classes, self::$resolvers['isAOf']);
444+
'isAnyOf' => function (Scope $scope, Arg $value, Arg $classes): ?Expr {
445+
return self::buildAnyOfExpr($scope, $value, $classes, $this->resolvers['isAOf']);
443446
},
444-
'isNotA' => static function (Scope $scope, Arg $value, Arg $class): Expr {
445-
return new BooleanNot(self::$resolvers['isAOf']($scope, $value, $class));
447+
'isNotA' => function (Scope $scope, Arg $value, Arg $class): Expr {
448+
return new BooleanNot($this->resolvers['isAOf']($scope, $value, $class));
446449
},
447-
'implementsInterface' => static function (Scope $scope, Arg $expr, Arg $class): ?Expr {
448-
$classType = $scope->getType($class->value);
449-
if (!$classType instanceof ConstantStringType) {
450+
'implementsInterface' => function (Scope $scope, Arg $expr, Arg $class): ?Expr {
451+
$classType = $scope->getType($class->value)->getClassStringObjectType();
452+
$classNames = $classType->getObjectClassNames();
453+
454+
if (count($classNames) !== 1) {
450455
return null;
451456
}
452457

453-
$classReflection = (new ObjectType($classType->getValue()))->getClassReflection();
454-
if ($classReflection === null) {
458+
if (!$this->reflectionProvider->hasClass($classNames[0])) {
455459
return null;
456460
}
457461

462+
$classReflection = $this->reflectionProvider->getClass($classNames[0]);
458463
if (!$classReflection->isInterface()) {
459464
return new ConstFetch(new Name('false'));
460465
}
461466

462-
return self::$resolvers['subclassOf']($scope, $expr, $class);
467+
return $this->resolvers['subclassOf']($scope, $expr, $class);
463468
},
464469
'keyExists' => static function (Scope $scope, Arg $array, Arg $key): Expr {
465470
return new FuncCall(
466471
new Name('array_key_exists'),
467472
[$key, $array]
468473
);
469474
},
470-
'keyNotExists' => static function (Scope $scope, Arg $array, Arg $key): Expr {
471-
return new BooleanNot(self::$resolvers['keyExists']($scope, $array, $key));
475+
'keyNotExists' => function (Scope $scope, Arg $array, Arg $key): Expr {
476+
return new BooleanNot($this->resolvers['keyExists']($scope, $array, $key));
472477
},
473478
'validArrayKey' => static function (Scope $scope, Arg $value): Expr {
474479
return new BooleanOr(
@@ -518,8 +523,8 @@ private static function getExpressionResolvers(): array
518523
$value2->value
519524
);
520525
},
521-
'notEq' => static function (Scope $scope, Arg $value, Arg $value2): Expr {
522-
return new BooleanNot(self::$resolvers['eq']($scope, $value, $value2));
526+
'notEq' => function (Scope $scope, Arg $value, Arg $value2): Expr {
527+
return new BooleanNot($this->resolvers['eq']($scope, $value, $value2));
523528
},
524529
'same' => static function (Scope $scope, Arg $value1, Arg $value2): Expr {
525530
return new Identical(
@@ -714,8 +719,8 @@ private static function getExpressionResolvers(): array
714719
]
715720
);
716721
},
717-
'oneOf' => static function (Scope $scope, Arg $needle, Arg $array): Expr {
718-
return self::$resolvers['inArray']($scope, $needle, $array);
722+
'oneOf' => function (Scope $scope, Arg $needle, Arg $array): Expr {
723+
return $this->resolvers['inArray']($scope, $needle, $array);
719724
},
720725
'methodExists' => static function (Scope $scope, Arg $object, Arg $method): Expr {
721726
return new FuncCall(
@@ -744,12 +749,12 @@ private static function getExpressionResolvers(): array
744749
];
745750

746751
foreach (['contains', 'startsWith', 'endsWith'] as $name) {
747-
self::$resolvers[$name] = static function (Scope $scope, Arg $value, Arg $subString) use ($name): array {
752+
$this->resolvers[$name] = function (Scope $scope, Arg $value, Arg $subString) use ($name): array {
748753
if ($scope->getType($subString->value)->isNonEmptyString()->yes()) {
749754
return self::createIsNonEmptyStringAndSomethingExprPair($name, [$value, $subString]);
750755
}
751756

752-
return [self::$resolvers['string']($scope, $value), null];
757+
return [$this->resolvers['string']($scope, $value), null];
753758
};
754759
}
755760

@@ -769,14 +774,14 @@ private static function getExpressionResolvers(): array
769774
'notWhitespaceOnly',
770775
];
771776
foreach ($assertionsResultingAtLeastInNonEmptyString as $name) {
772-
self::$resolvers[$name] = static function (Scope $scope, Arg $value) use ($name): array {
777+
$this->resolvers[$name] = static function (Scope $scope, Arg $value) use ($name): array {
773778
return self::createIsNonEmptyStringAndSomethingExprPair($name, [$value]);
774779
};
775780
}
776781

777782
}
778783

779-
return self::$resolvers;
784+
return $this->resolvers;
780785
}
781786

782787
private function handleAllNot(
@@ -797,20 +802,17 @@ static function (Type $type): Type {
797802

798803
if ($methodName === 'allNotInstanceOf') {
799804
$classType = $scope->getType($node->getArgs()[1]->value);
800-
801-
if ($classType instanceof ConstantStringType) {
802-
$objectType = new ObjectType($classType->getValue());
803-
} elseif ($classType instanceof TypeWithClassName) {
804-
$objectType = new ObjectType($classType->getClassName());
805-
} else {
805+
$classNameType = $classType->getObjectTypeOrClassStringObjectType();
806+
$classNames = $classNameType->getObjectClassNames();
807+
if (count($classNames) !== 1) {
806808
return new SpecifiedTypes([], []);
807809
}
808810

809811
return $this->allArrayOrIterable(
810812
$scope,
811813
$node->getArgs()[0]->value,
812-
static function (Type $type) use ($objectType): Type {
813-
return TypeCombinator::remove($type, $objectType);
814+
static function (Type $type) use ($classNameType): Type {
815+
return TypeCombinator::remove($type, $classNameType);
814816
}
815817
);
816818
}
@@ -889,14 +891,15 @@ private function allArrayOrIterable(
889891
if (count($arrayTypes) > 0) {
890892
$newArrayTypes = [];
891893
foreach ($arrayTypes as $arrayType) {
892-
if ($arrayType instanceof ConstantArrayType) {
894+
$constantArrays = $arrayType->getConstantArrays();
895+
if (count($constantArrays) === 1) {
893896
$builder = ConstantArrayTypeBuilder::createEmpty();
894-
foreach ($arrayType->getKeyTypes() as $i => $keyType) {
895-
$valueType = $typeCallback($arrayType->getValueTypes()[$i]);
897+
foreach ($constantArrays[0]->getKeyTypes() as $i => $keyType) {
898+
$valueType = $typeCallback($constantArrays[0]->getValueTypes()[$i]);
896899
if ($valueType instanceof NeverType) {
897900
continue 2;
898901
}
899-
$builder->setOffsetValueType($keyType, $valueType, $arrayType->isOptionalKey($i));
902+
$builder->setOffsetValueType($keyType, $valueType, $constantArrays[0]->isOptionalKey($i));
900903
}
901904
$newArrayTypes[] = $builder->getArray();
902905
} else {

0 commit comments

Comments
 (0)