-
Notifications
You must be signed in to change notification settings - Fork 474
Commit
ArrayAccess->offsetExists
narrowing
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Type\Php; | ||
|
||
use ArrayAccess; | ||
use PhpParser\Node\Expr\MethodCall; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Analyser\SpecifiedTypes; | ||
use PHPStan\Analyser\TypeSpecifier; | ||
use PHPStan\Analyser\TypeSpecifierAwareExtension; | ||
use PHPStan\Analyser\TypeSpecifierContext; | ||
use PHPStan\Reflection\MethodReflection; | ||
use PHPStan\Reflection\ReflectionProvider; | ||
use PHPStan\Type\Accessory\HasOffsetValueType; | ||
use PHPStan\Type\Constant\ConstantIntegerType; | ||
use PHPStan\Type\Constant\ConstantStringType; | ||
use PHPStan\Type\Generic\GenericObjectType; | ||
use PHPStan\Type\MethodTypeSpecifyingExtension; | ||
use function count; | ||
|
||
final class ArrayAccessOffsetExistsMethodTypeSpecifyingExtension implements MethodTypeSpecifyingExtension, TypeSpecifierAwareExtension | ||
{ | ||
|
||
private TypeSpecifier $typeSpecifier; | ||
|
||
public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void | ||
{ | ||
$this->typeSpecifier = $typeSpecifier; | ||
} | ||
|
||
public function getClass(): string | ||
{ | ||
return ArrayAccess::class; | ||
} | ||
|
||
public function isMethodSupported( | ||
MethodReflection $methodReflection, | ||
MethodCall $node, | ||
TypeSpecifierContext $context, | ||
): bool | ||
{ | ||
return $methodReflection->getName() === 'offsetExists' && $context->true(); | ||
} | ||
|
||
public function specifyTypes(MethodReflection $methodReflection, MethodCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes | ||
{ | ||
if (count($node->getArgs()) < 1) { | ||
return new SpecifiedTypes(); | ||
} | ||
$key = $node->getArgs()[0]->value; | ||
Check failure on line 50 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (7.4, ubuntu-latest)
Check failure on line 50 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (7.4, windows-latest)
|
||
$keyType = $scope->getType($key); | ||
|
||
if ( | ||
!$keyType instanceof ConstantStringType | ||
Check failure on line 54 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan with result cache (8.1)
Check failure on line 54 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (8.4, ubuntu-latest)
Check failure on line 54 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan with result cache (8.3)
Check failure on line 54 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (8.1, ubuntu-latest)
Check failure on line 54 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (8.3, ubuntu-latest)
Check failure on line 54 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan with result cache (8.2)
Check failure on line 54 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (8.2, ubuntu-latest)
Check failure on line 54 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (8.0, ubuntu-latest)
Check failure on line 54 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan with result cache (8.4)
Check failure on line 54 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (8.1, windows-latest)
Check failure on line 54 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (8.2, windows-latest)
Check failure on line 54 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (8.4, windows-latest)
Check failure on line 54 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (8.3, windows-latest)
Check failure on line 54 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (8.0, windows-latest)
|
||
&& !$keyType instanceof ConstantIntegerType | ||
) { | ||
return new SpecifiedTypes(); | ||
} | ||
|
||
foreach ($scope->getType($node->var)->getObjectClassReflections() as $classReflection) { | ||
$implementsTags = $classReflection->getImplementsTags(); | ||
Check failure on line 61 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (7.4, ubuntu-latest)
|
||
|
||
if ( | ||
!isset($implementsTags[ArrayAccess::class]) | ||
|| !$implementsTags[ArrayAccess::class]->getType() instanceof GenericObjectType | ||
Check failure on line 65 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan with result cache (8.1)
Check failure on line 65 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (8.4, ubuntu-latest)
Check failure on line 65 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan with result cache (8.3)
Check failure on line 65 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (8.1, ubuntu-latest)
Check failure on line 65 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (8.3, ubuntu-latest)
Check failure on line 65 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan with result cache (8.2)
Check failure on line 65 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (8.2, ubuntu-latest)
Check failure on line 65 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (8.0, ubuntu-latest)
Check failure on line 65 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan with result cache (8.4)
Check failure on line 65 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (8.1, windows-latest)
Check failure on line 65 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (8.2, windows-latest)
Check failure on line 65 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (8.4, windows-latest)
Check failure on line 65 in src/Type/Php/ArrayAccessOffsetExistsMethodTypeSpecifyingExtension.php GitHub Actions / PHPStan (8.3, windows-latest)
|
||
) { | ||
continue; | ||
} | ||
|
||
$implementsType = $implementsTags[ArrayAccess::class]->getType(); | ||
$arrayAccessGenericTypes = $implementsType->getTypes(); | ||
if (!isset($arrayAccessGenericTypes[1])) { | ||
continue; | ||
} | ||
|
||
return $this->typeSpecifier->create( | ||
$node->var, | ||
new HasOffsetValueType($keyType, $arrayAccessGenericTypes[1]), | ||
$context, | ||
$scope, | ||
); | ||
} | ||
|
||
return new SpecifiedTypes(); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Type\Php; | ||
|
||
use ArrayAccess; | ||
use PhpParser\Node\Expr\MethodCall; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Reflection\MethodReflection; | ||
use PHPStan\Type\DynamicMethodReturnTypeExtension; | ||
use PHPStan\Type\Type; | ||
use function count; | ||
|
||
final class ArrayAccessOffsetGetMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension | ||
{ | ||
|
||
public function getClass(): string | ||
{ | ||
return ArrayAccess::class; | ||
} | ||
|
||
public function isMethodSupported( | ||
MethodReflection $methodReflection, | ||
): bool | ||
{ | ||
return $methodReflection->getName() === 'offsetGet'; | ||
} | ||
|
||
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type | ||
{ | ||
if (count($methodCall->getArgs()) < 1) { | ||
return null; | ||
} | ||
$key = $methodCall->getArgs()[0]->value; | ||
$keyType = $scope->getType($key); | ||
$objectType = $scope->getType($methodCall->var); | ||
|
||
if (!$objectType->hasOffsetValueType($keyType)->yes()) { | ||
return null; | ||
} | ||
|
||
return $objectType->getOffsetValueType($keyType); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<?php | ||
|
||
namespace Bug3323; | ||
|
||
use function PHPStan\Testing\assertType; | ||
use ArrayAccess; | ||
|
||
/** | ||
* @implements ArrayAccess<string, self> | ||
*/ | ||
class FormView implements \ArrayAccess | ||
{ | ||
public array $vars = []; | ||
|
||
public function offsetExists($offset) { | ||
return array_key_exists($offset, $this->vars); | ||
} | ||
public function offsetGet($offset) { | ||
return $this->vars[$offset] ?? null; | ||
} | ||
public function offsetSet($offset, $value) { | ||
$this->vars[$offset] = $value; | ||
} | ||
public function offsetUnset($offset) { | ||
unset($this->vars[$offset]); | ||
} | ||
} | ||
|
||
function doFoo() { | ||
$formView = new FormView(); | ||
assertType('Bug3323\FormView', $formView); | ||
if ($formView->offsetExists('_token')) { | ||
assertType("Bug3323\FormView&hasOffsetValue('_token', Bug3323\FormView)", $formView); | ||
|
||
$a = $formView->offsetGet('_token'); | ||
assertType("Bug3323\FormView", $a); | ||
|
||
$a = $formView->offsetGet(123); | ||
assertType("Bug3323\FormView|null", $a); | ||
} else { | ||
assertType('Bug3323\FormView', $formView); | ||
} | ||
assertType('Bug3323\FormView', $formView); | ||
|
||
$a = $formView->offsetGet('_token'); | ||
assertType("Bug3323\FormView|null", $a); | ||
|
||
$a = $formView->offsetGet(123); | ||
assertType("Bug3323\FormView|null", $a); | ||
} | ||
|