-
Notifications
You must be signed in to change notification settings - Fork 462
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
Implement offset/index access type #1318
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Type\Generic; | ||
|
||
use PHPStan\Type\KeyOfType; | ||
use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; | ||
use PHPStan\Type\Type; | ||
|
||
/** @api */ | ||
final class TemplateKeyOfType extends KeyOfType implements TemplateType | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's weird to add support for key-of in generics but not for value-of at the same time :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did have it implemented, but I wasn't able to think of a good use-case for it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't either but it looks like an ommission not to have it :) |
||
{ | ||
|
||
/** @use TemplateTypeTrait<KeyOfType> */ | ||
use TemplateTypeTrait; | ||
use UndecidedComparisonCompoundTypeTrait; | ||
|
||
public function __construct( | ||
TemplateTypeScope $scope, | ||
TemplateTypeStrategy $templateTypeStrategy, | ||
TemplateTypeVariance $templateTypeVariance, | ||
string $name, | ||
KeyOfType $bound, | ||
) | ||
{ | ||
parent::__construct($bound->getType()); | ||
$this->scope = $scope; | ||
$this->strategy = $templateTypeStrategy; | ||
$this->variance = $templateTypeVariance; | ||
$this->name = $name; | ||
$this->bound = $bound; | ||
} | ||
|
||
public function traverse(callable $cb): Type | ||
{ | ||
$newBound = $cb($this->getBound()); | ||
if ($this->getBound() !== $newBound && $newBound instanceof KeyOfType) { | ||
return new self( | ||
$this->scope, | ||
$this->strategy, | ||
$this->variance, | ||
$this->name, | ||
$newBound, | ||
); | ||
} | ||
|
||
return $this; | ||
} | ||
|
||
protected function shouldGeneralizeInferredType(): bool | ||
{ | ||
return false; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,7 @@ | |
use function sprintf; | ||
|
||
/** @api */ | ||
final class KeyOfType implements CompoundType, LateResolvableType | ||
class KeyOfType implements CompoundType, LateResolvableType | ||
{ | ||
|
||
use LateResolvableTypeTrait; | ||
|
@@ -18,6 +18,11 @@ public function __construct(private Type $type) | |
{ | ||
} | ||
|
||
public function getType(): Type | ||
{ | ||
return $this->type; | ||
} | ||
|
||
public function getReferencedClasses(): array | ||
{ | ||
return $this->type->getReferencedClasses(); | ||
|
@@ -41,7 +46,7 @@ public function describe(VerbosityLevel $level): string | |
|
||
public function isResolvable(): bool | ||
{ | ||
return !TypeUtils::containsTemplateType($this->type); | ||
return !TypeUtils::containsTemplateType($this->type) && $this->type->isIterable()->yes(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't necessary, right? For example ArrayAccess has "keys" but isn't iterable unless paired with Traversable. |
||
} | ||
|
||
protected function getResult(): Type | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Type; | ||
|
||
use PHPStan\Type\Generic\TemplateTypeMap; | ||
use PHPStan\Type\Generic\TemplateTypeVariance; | ||
use PHPStan\Type\Traits\LateResolvableTypeTrait; | ||
use PHPStan\Type\Traits\NonGeneralizableTypeTrait; | ||
use function array_merge; | ||
use function sprintf; | ||
|
||
/** @api */ | ||
final class OffsetAccessType implements CompoundType, LateResolvableType | ||
{ | ||
|
||
use LateResolvableTypeTrait; | ||
use NonGeneralizableTypeTrait; | ||
|
||
public function __construct( | ||
private Type $type, | ||
private Type $offset, | ||
) | ||
{ | ||
} | ||
|
||
public function getReferencedClasses(): array | ||
{ | ||
return array_merge( | ||
$this->type->getReferencedClasses(), | ||
$this->offset->getReferencedClasses(), | ||
); | ||
} | ||
|
||
public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array | ||
{ | ||
return array_merge( | ||
$this->type->getReferencedTemplateTypes($positionVariance), | ||
$this->offset->getReferencedTemplateTypes($positionVariance), | ||
); | ||
} | ||
|
||
public function equals(Type $type): bool | ||
{ | ||
return $type instanceof self | ||
&& $this->type->equals($type->type) | ||
&& $this->offset->equals($type->offset); | ||
} | ||
|
||
public function describe(VerbosityLevel $level): string | ||
{ | ||
return sprintf( | ||
'%s[%s]', | ||
$this->type->describe($level), | ||
$this->offset->describe($level), | ||
); | ||
} | ||
|
||
public function isResolvable(): bool | ||
{ | ||
return !TypeUtils::containsTemplateType($this->type) | ||
&& !TypeUtils::containsTemplateType($this->offset) | ||
&& $this->type->hasOffsetValueType($this->offset)->yes(); | ||
} | ||
|
||
protected function getResult(): Type | ||
{ | ||
return $this->type->getOffsetValueType($this->offset); | ||
} | ||
|
||
public function inferTemplateTypes(Type $receivedType): TemplateTypeMap | ||
{ | ||
if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { | ||
return $receivedType->inferTemplateTypesOn($this); | ||
} | ||
|
||
$typeMap = $this->type->inferTemplateTypes($receivedType); | ||
$offsetMap = $this->offset->inferTemplateTypes($receivedType); | ||
|
||
return $typeMap->union($offsetMap); | ||
} | ||
|
||
/** | ||
* @param callable(Type): Type $cb | ||
*/ | ||
public function traverse(callable $cb): Type | ||
{ | ||
$type = $cb($this->type); | ||
$offset = $cb($this->offset); | ||
|
||
if ($this->type === $type && $this->offset === $offset) { | ||
return $this; | ||
} | ||
|
||
return new OffsetAccessType($type, $offset); | ||
} | ||
|
||
/** | ||
* @param mixed[] $properties | ||
*/ | ||
public static function __set_state(array $properties): Type | ||
{ | ||
return new self( | ||
$properties['type'], | ||
$properties['offset'], | ||
); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,7 +41,7 @@ public function describe(VerbosityLevel $level): string | |
|
||
public function isResolvable(): bool | ||
{ | ||
return !TypeUtils::containsTemplateType($this->type); | ||
return !TypeUtils::containsTemplateType($this->type) && $this->type->isIterable()->yes(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dtto |
||
} | ||
|
||
protected function getResult(): Type | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TypeNodeResolver could detect when the type doesn't make sense. For example when
$type
isn't offset-accessible or when$type–>hasOffsetValueType($offset)
isno()
. It could returnnew ErrorType()
in that case and the appropriate rules will pick this ErrorType and complain about an "unresolvable type" automatically.