Skip to content

Commit 0d9c061

Browse files
committed
property-of<object> initial
1 parent 2d24ffc commit 0d9c061

File tree

3 files changed

+163
-1
lines changed

3 files changed

+163
-1
lines changed

src/PhpDoc/TypeNodeResolver.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
use PHPStan\Type\ObjectType;
9393
use PHPStan\Type\ObjectWithoutClassType;
9494
use PHPStan\Type\OffsetAccessType;
95+
use PHPStan\Type\PropertyOfType;
9596
use PHPStan\Type\ResourceType;
9697
use PHPStan\Type\StaticType;
9798
use PHPStan\Type\StaticTypeFactory;
@@ -760,7 +761,15 @@ static function (string $variance): TemplateTypeVariance {
760761
}
761762

762763
return new ErrorType();
763-
} elseif ($mainTypeName === 'int-mask-of') {
764+
} elseif ($mainTypeName === 'property-of') {
765+
if (count($genericTypes) === 1) { // property-of<ValueType>
766+
$type = new PropertyOfType($genericTypes[0]);
767+
768+
return $type->isResolvable() ? $type->resolve() : $type;
769+
}
770+
771+
return new ErrorType();
772+
} elseif ($mainTypeName === 'int-mask-of') {
764773
if (count($genericTypes) === 1) { // int-mask-of<Class::CONST*>
765774
$maskType = $this->expandIntMaskToType($genericTypes[0]);
766775
if ($maskType !== null) {

src/Type/PropertyOfType.php

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
namespace PHPStan\Type;
4+
5+
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
6+
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
7+
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
8+
use PHPStan\Type\Constant\ConstantStringType;
9+
use PHPStan\Type\Generic\TemplateTypeVariance;
10+
use PHPStan\Type\Traits\LateResolvableTypeTrait;
11+
use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
12+
13+
class PropertyOfType implements CompoundType, LateResolvableType
14+
{
15+
16+
use LateResolvableTypeTrait;
17+
use NonGeneralizableTypeTrait;
18+
19+
public function __construct(private Type $type)
20+
{
21+
}
22+
23+
public function getType(): Type
24+
{
25+
return $this->type;
26+
}
27+
28+
public function getReferencedClasses(): array
29+
{
30+
return $this->type->getReferencedClasses();
31+
}
32+
33+
public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array
34+
{
35+
return $this->type->getReferencedTemplateTypes($positionVariance);
36+
}
37+
38+
public function equals(Type $type): bool
39+
{
40+
return $type instanceof self
41+
&& $this->type->equals($type->type);
42+
}
43+
44+
public function describe(VerbosityLevel $level): string
45+
{
46+
return sprintf('property-of<%s>', $this->type->describe($level));
47+
}
48+
49+
public function isResolvable(): bool
50+
{
51+
return !TypeUtils::containsTemplateType($this->type);
52+
}
53+
54+
protected function getResult(): Type
55+
{
56+
57+
$classReflection = null;
58+
59+
if ($this->type instanceof TypeWithClassName) {
60+
$classReflection = $this->type->getClassReflection();
61+
}
62+
63+
if ($classReflection !== null) {
64+
65+
$propertiesReflection = $classReflection->getNativeReflection()->getProperties();
66+
67+
// get the names of the properties
68+
// and build a union type from them
69+
$propertyNames = array_map(
70+
fn($property) => new ConstantStringType($property->getName()),
71+
$propertiesReflection
72+
);
73+
74+
return new UnionType($propertyNames);
75+
76+
}
77+
78+
return new MixedType();
79+
}
80+
81+
/**
82+
* @param callable(Type): Type $cb
83+
*/
84+
public function traverse(callable $cb): Type
85+
{
86+
$type = $cb($this->type);
87+
88+
if ($this->type === $type) {
89+
return $this;
90+
}
91+
92+
return new self($type);
93+
}
94+
95+
public function traverseSimultaneously(Type $right, callable $cb): Type
96+
{
97+
if (!$right instanceof self) {
98+
return $this;
99+
}
100+
101+
$type = $cb($this->type, $right->type);
102+
103+
if ($this->type === $type) {
104+
return $this;
105+
}
106+
107+
return new self($type);
108+
}
109+
110+
public function toPhpDocNode(): TypeNode
111+
{
112+
return new GenericTypeNode(new IdentifierTypeNode('property-of'), [$this->type->toPhpDocNode()]);
113+
}
114+
115+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace PropertyOfType;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
10+
public int $age;
11+
public string $name;
12+
13+
/**
14+
* @param property-of<self> $property
15+
*/
16+
public static function fromObject(string $property): void
17+
{
18+
assertType("'age'|'name'", $property);
19+
}
20+
21+
/**
22+
* @param property-of<static> $property
23+
*/
24+
public static function fromStatic(string $property): void
25+
{
26+
assertType("'age'|'name'", $property);
27+
}
28+
29+
30+
/**
31+
* @param property-of<Foo> $property
32+
*/
33+
public static function fromClass(string $property): void
34+
{
35+
assertType("'age'|'name'", $property);
36+
}
37+
38+
}

0 commit comments

Comments
 (0)