Skip to content

Improve the InspectorTypeExtension and its tests #886

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

Open
wants to merge 2 commits into
base: 1.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 76 additions & 65 deletions src/Type/InspectorTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\UnionType;
use Stringable;
use function class_exists;
use function interface_exists;

final class InspectorTypeExtension implements StaticMethodTypeSpecifyingExtension, TypeSpecifierAwareExtension
{
Expand Down Expand Up @@ -100,20 +98,20 @@ public function specifyTypes(MethodReflection $staticMethodReflection, StaticCal
*/
private function specifyAssertAll(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
$callable = $node->getArgs()[0]->value;
$callableInfo = $scope->getType($callable);
$callableArg = $node->getArgs()[0]->value;
$callableType = $scope->getType($callableArg);

if (!$callableInfo->isCallable()->yes()) {
if (!$callableType->isCallable()->yes()) {
return new SpecifiedTypes();
}

$traversable = $node->getArgs()[1]->value;
$traversableInfo = $scope->getType($traversable);
$traversableArg = $node->getArgs()[1]->value;
$traversableType = $scope->getType($traversableArg);

// If it is already not mixed (narrowed by other code, like
// '::assertAllArray()'), we could not provide any additional
// information. We can only narrow this method to 'array<mixed, mixed>'.
if (!$traversableInfo->equals(new MixedType())) {
if (!$traversableType->equals(new MixedType())) {
return new SpecifiedTypes();
}

Expand All @@ -130,23 +128,24 @@ private function specifyAssertAll(MethodReflection $staticMethodReflection, Stat
return new SpecifiedTypes();
}

return $this->typeSpecifier->create(
$node->getArgs()[1]->value,
new IterableType(new MixedType(), new MixedType()),
$context,
$newType = new IterableType(new MixedType(), new MixedType());

return $this->typeSpecifier->create($traversableArg, $newType, $context,
false,
$scope,
);
$scope);
}

/**
* @see Drupal\Component\Assertion\Inspector::assertAllStrings()
*/
private function specifyAssertAllStrings(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
$traversableArg = $node->getArgs()[0]->value;
$newType = new IterableType(new MixedType(), new StringType());

return $this->typeSpecifier->create(
$node->getArgs()[0]->value,
new IterableType(new MixedType(), new StringType()),
$traversableArg,
$newType,
$context,
false,
$scope,
Expand All @@ -158,62 +157,61 @@ private function specifyAssertAllStrings(MethodReflection $staticMethodReflectio
*/
private function specifyAssertAllStringable(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
$traversableArg = $node->getArgs()[0]->value;
// Drupal considers string as part of "stringable" as well.
$stringable = TypeCombinator::union(new ObjectType(Stringable::class), new StringType());
$newType = new IterableType(new MixedType(), $stringable);
$stringableType = TypeCombinator::union(new ObjectType(Stringable::class), new StringType());
$newType = new IterableType(new MixedType(), $stringableType);

return $this->typeSpecifier->create(
$node->getArgs()[0]->value,
return $this->typeSpecifier->create($traversableArg,
$newType,
$context,
false,
$scope,
);
$scope);
}

/**
* @see Drupal\Component\Assertion\Inspector::assertAllArrays()
*/
private function specifyAssertAllArrays(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
$traversableArg = $node->getArgs()[0]->value;
$arrayType = new ArrayType(new MixedType(), new MixedType());
$newType = new IterableType(new MixedType(), $arrayType);

return $this->typeSpecifier->create(
$node->getArgs()[0]->value,
return $this->typeSpecifier->create($traversableArg,
$newType,
$context,
false,
$scope,
);
$scope);
}

/**
* @see Drupal\Component\Assertion\Inspector::assertStrictArray()
*/
private function specifyAssertStrictArray(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
$traversableArg = $node->getArgs()[0]->value;
$newType = new ArrayType(
// In Drupal, 'strict arrays' are defined as arrays whose indexes
// consist of integers that are equal to or greater than 0.
// In Drupal, 'strict arrays' are defined as arrays whose
// indexes consist of integers that are equal to or greater
// than 0.
IntegerRangeType::createAllGreaterThanOrEqualTo(0),
new MixedType(),
);

return $this->typeSpecifier->create(
$node->getArgs()[0]->value,
return $this->typeSpecifier->create($traversableArg,
$newType,
$context,
false,
$scope,
);
$scope);
}

/**
* @see Drupal\Component\Assertion\Inspector::assertAllStrictArrays()
*/
private function specifyAssertAllStrictArrays(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
$traversableArg = $node->getArgs()[0]->value;
$newType = new IterableType(
new MixedType(),
new ArrayType(
Expand All @@ -222,13 +220,11 @@ private function specifyAssertAllStrictArrays(MethodReflection $staticMethodRefl
),
);

return $this->typeSpecifier->create(
$node->getArgs()[0]->value,
return $this->typeSpecifier->create($traversableArg,
$newType,
$context,
false,
$scope,
);
$scope);
}

/**
Expand Down Expand Up @@ -278,9 +274,12 @@ private function specifyAssertAllHaveKey(MethodReflection $staticMethodReflectio
*/
private function specifyAssertAllIntegers(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
$traversableArg = $node->getArgs()[0]->value;
$newType = new IterableType(new MixedType(), new IntegerType());

return $this->typeSpecifier->create(
$node->getArgs()[0]->value,
new IterableType(new MixedType(), new IntegerType()),
$traversableArg,
$newType,
$context,
false,
$scope,
Expand All @@ -292,9 +291,12 @@ private function specifyAssertAllIntegers(MethodReflection $staticMethodReflecti
*/
private function specifyAssertAllFloat(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
$traversableArg = $node->getArgs()[0]->value;
$newType = new IterableType(new MixedType(), new FloatType());

return $this->typeSpecifier->create(
$node->getArgs()[0]->value,
new IterableType(new MixedType(), new FloatType()),
$traversableArg,
$newType,
$context,
false,
$scope,
Expand All @@ -306,9 +308,12 @@ private function specifyAssertAllFloat(MethodReflection $staticMethodReflection,
*/
private function specifyAssertAllCallable(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
$traversableArg = $node->getArgs()[0]->value;
$newType = new IterableType(new MixedType(), new CallableType());

return $this->typeSpecifier->create(
$node->getArgs()[0]->value,
new IterableType(new MixedType(), new CallableType()),
$traversableArg,
$newType,
$context,
false,
$scope,
Expand All @@ -320,7 +325,8 @@ private function specifyAssertAllCallable(MethodReflection $staticMethodReflecti
*/
private function specifyAssertAllNotEmpty(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
$non_empty_types = [
$traversableArg = $node->getArgs()[0]->value;
$nonEmptyTypes = [
new NonEmptyArrayType(),
new ObjectType('object'),
new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]),
Expand All @@ -329,25 +335,26 @@ private function specifyAssertAllNotEmpty(MethodReflection $staticMethodReflecti
new FloatType(),
new ResourceType(),
];
$newType = new IterableType(new MixedType(), new UnionType($non_empty_types));
$newType = new IterableType(new MixedType(), new UnionType($nonEmptyTypes));

return $this->typeSpecifier->create(
$node->getArgs()[0]->value,
return $this->typeSpecifier->create($traversableArg,
$newType,
$context,
false,
$scope,
);
$scope);
}

/**
* @see Drupal\Component\Assertion\Inspector::assertAllNumeric()
*/
private function specifyAssertAllNumeric(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
$traversableArg = $node->getArgs()[0]->value;
$newType = new IterableType(new MixedType(), new UnionType([new IntegerType(), new FloatType()]));

return $this->typeSpecifier->create(
$node->getArgs()[0]->value,
new IterableType(new MixedType(), new UnionType([new IntegerType(), new FloatType()])),
$traversableArg,
$newType,
$context,
false,
$scope,
Expand All @@ -359,9 +366,12 @@ private function specifyAssertAllNumeric(MethodReflection $staticMethodReflectio
*/
private function specifyAssertAllMatch(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
$traversableArg = $node->getArgs()[1]->value;
$newType = new IterableType(new MixedType(), new StringType());

return $this->typeSpecifier->create(
$node->getArgs()[1]->value,
new IterableType(new MixedType(), new StringType()),
$traversableArg,
$newType,
$context,
false,
$scope,
Expand All @@ -373,11 +383,14 @@ private function specifyAssertAllMatch(MethodReflection $staticMethodReflection,
*/
private function specifyAssertAllRegularExpressionMatch(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
$traversableArg = $node->getArgs()[1]->value;
// Drupal treats any non-string input in traversable as invalid
// value, so it is possible to narrow type here.
$newType = new IterableType(new MixedType(), new StringType());

return $this->typeSpecifier->create(
$node->getArgs()[1]->value,
// Drupal treats any non-string input in traversable as invalid
// value, so it is possible to narrow type here.
new IterableType(new MixedType(), new StringType()),
$traversableArg,
$newType,
$context,
false,
$scope,
Expand All @@ -398,20 +411,18 @@ private function specifyAssertAllObjects(MethodReflection $staticMethodReflectio

$argType = $scope->getType($arg->value);
foreach ($argType->getConstantStrings() as $stringType) {
$classString = $stringType->getValue();
// PHPStan does not recognize a string argument like '\\Stringable'
// as a class string, so we need to explicitly check it.
if (!class_exists($classString) && !interface_exists($classString)) {
continue;
if ($stringType->isClassString()->yes()) {
$objectTypes[] = new ObjectType($stringType->getValue());
}

$objectTypes[] = new ObjectType($classString);
}
}

$traversableArg = $node->getArgs()[0]->value;
$newType = new IterableType(new MixedType(), TypeCombinator::union(...$objectTypes));

return $this->typeSpecifier->create(
$node->getArgs()[0]->value,
new IterableType(new MixedType(), TypeCombinator::union(...$objectTypes)),
$traversableArg,
$newType,
$context,
false,
$scope,
Expand Down
Loading
Loading