Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 1f95482

Browse files
ruudkrvanvelzen
authored andcommitted
Introduce new type
This makes it possible that a new instance of a class-string will be returned. ```php /** * @var array<string, class-string> */ private const TYPES = [ 'foo' => DateTime::class, 'bar' => DateTimeImmutable::class, ]; /** * @template T of key-of<self::TYPES> * @param T $type * * @return new<self::TYPES[T]> */ public static function get(string $type) : ?object { $class = self::TYPES[$type]; return new $class('now'); } ``` See phpstan/phpstan#9704 The work was done by @rvanvelzen in a gist. I just created the PR for it. Co-Authored-By: Richard van Velzen <rvanvelzen1@gmail.com>
1 parent 1090835 commit 1f95482

File tree

4 files changed

+167
-0
lines changed

4 files changed

+167
-0
lines changed

‎src/PhpDoc/TypeNodeResolver.php‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
use PHPStan\Type\IterableType;
8181
use PHPStan\Type\KeyOfType;
8282
use PHPStan\Type\MixedType;
83+
use PHPStan\Type\NewObjectType;
8384
use PHPStan\Type\NonAcceptingNeverType;
8485
use PHPStan\Type\NonexistentParentClassType;
8586
use PHPStan\Type\NullType;
@@ -755,6 +756,13 @@ static function (string $variance): TemplateTypeVariance {
755756
return TypeCombinator::union(...$result);
756757
}
757758

759+
return new ErrorType();
760+
} elseif ($mainTypeName === 'new') {
761+
if (count($genericTypes) === 1) {
762+
$type = new NewObjectType($genericTypes[0]);
763+
return $type->isResolvable() ? $type->resolve() : $type;
764+
}
765+
758766
return new ErrorType();
759767
}
760768

‎src/Type/NewObjectType.php‎

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php declare(strict_types = 1);
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\Generic\TemplateTypeVariance;
9+
use PHPStan\Type\Traits\LateResolvableTypeTrait;
10+
use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
11+
use function sprintf;
12+
13+
/** @api */
14+
class NewObjectType implements CompoundType, LateResolvableType
15+
{
16+
17+
use LateResolvableTypeTrait;
18+
use NonGeneralizableTypeTrait;
19+
20+
public function __construct(private Type $type)
21+
{
22+
}
23+
24+
public function getType(): Type
25+
{
26+
return $this->type;
27+
}
28+
29+
public function getReferencedClasses(): array
30+
{
31+
return $this->type->getReferencedClasses();
32+
}
33+
34+
public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array
35+
{
36+
return $this->type->getReferencedTemplateTypes($positionVariance);
37+
}
38+
39+
public function equals(Type $type): bool
40+
{
41+
return $type instanceof self
42+
&& $this->type->equals($type->type);
43+
}
44+
45+
public function describe(VerbosityLevel $level): string
46+
{
47+
return sprintf('new<%s>', $this->type->describe($level));
48+
}
49+
50+
public function isResolvable(): bool
51+
{
52+
return !TypeUtils::containsTemplateType($this->type);
53+
}
54+
55+
protected function getResult(): Type
56+
{
57+
return $this->type->getObjectTypeOrClassStringObjectType();
58+
}
59+
60+
/**
61+
* @param callable(Type): Type $cb
62+
*/
63+
public function traverse(callable $cb): Type
64+
{
65+
$type = $cb($this->type);
66+
67+
if ($this->type === $type) {
68+
return $this;
69+
}
70+
71+
return new self($type);
72+
}
73+
74+
public function traverseSimultaneously(Type $right, callable $cb): Type
75+
{
76+
if (!$right instanceof self) {
77+
return $this;
78+
}
79+
80+
$type = $cb($this->type, $right->type);
81+
82+
if ($this->type === $type) {
83+
return $this;
84+
}
85+
86+
return new self($type);
87+
}
88+
89+
public function toPhpDocNode(): TypeNode
90+
{
91+
return new GenericTypeNode(new IdentifierTypeNode('new'), [$this->type->toPhpDocNode()]);
92+
}
93+
94+
/**
95+
* @param mixed[] $properties
96+
*/
97+
public static function __set_state(array $properties): Type
98+
{
99+
return new self(
100+
$properties['type'],
101+
);
102+
}
103+
104+
}

‎tests/PHPStan/Analyser/NodeScopeResolverTest.php‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,7 @@ public function dataFileAsserts(): iterable
773773
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4357.php');
774774
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10863.php');
775775
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5817.php');
776+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-9704.php');
776777

777778
yield from $this->gatherAssertTypes(__DIR__ . '/data/array-chunk.php');
778779
if (PHP_VERSION_ID >= 80000) {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace Bug9704;
4+
5+
use DateTime;
6+
use DateTimeImmutable;
7+
use function PHPStan\dumpType;
8+
use function PHPStan\Testing\assertType;
9+
10+
class Foo
11+
{
12+
/**
13+
* @var array<string, class-string>
14+
*/
15+
private const TYPES = [
16+
'foo' => DateTime::class,
17+
'bar' => DateTimeImmutable::class,
18+
];
19+
20+
/**
21+
* @template M of self::TYPES
22+
* @template T of key-of<M>
23+
* @param T $type
24+
*
25+
* @return new<M[T]>
26+
*/
27+
public static function get(string $type) : object
28+
{
29+
$class = self::TYPES[$type];
30+
31+
return new $class('now');
32+
}
33+
34+
/**
35+
* @template T of key-of<self::TYPES>
36+
* @param T $type
37+
*
38+
* @return new<self::TYPES[T]>
39+
*/
40+
public static function get2(string $type) : object
41+
{
42+
$class = self::TYPES[$type];
43+
44+
return new $class('now');
45+
}
46+
}
47+
48+
assertType(DateTime::class, Foo::get('foo'));
49+
assertType(DateTimeImmutable::class, Foo::get('bar'));
50+
51+
assertType(DateTime::class, Foo::get2('foo'));
52+
assertType(DateTimeImmutable::class, Foo::get2('bar'));
53+
54+

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /