Skip to content

Commit c205015

Browse files
committed
Merge branch 2.1.x into 2.2.x
2 parents c94eef1 + 42afbe6 commit c205015

File tree

5 files changed

+194
-2
lines changed

5 files changed

+194
-2
lines changed

src/Type/UnionType.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,11 @@ public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAcce
590590
continue;
591591
}
592592

593-
$methodPrototypes[] = $type->getUnresolvedMethodPrototype($methodName, $scope)->withCalledOnType($this);
593+
$prototype = $type->getUnresolvedMethodPrototype($methodName, $scope);
594+
if ($this instanceof TemplateType) {
595+
$prototype = $prototype->withCalledOnType($this);
596+
}
597+
$methodPrototypes[] = $prototype;
594598
}
595599

596600
$methodsCount = count($methodPrototypes);
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug11687;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class A
8+
{
9+
public static function retStaticConst(): int
10+
{
11+
return 1;
12+
}
13+
14+
/**
15+
* @return static
16+
*/
17+
public static function retStatic()
18+
{
19+
return new static(); // @phpstan-ignore new.static
20+
}
21+
}
22+
23+
class B extends A
24+
{
25+
/**
26+
* @return 2
27+
*/
28+
public static function retStaticConst(): int
29+
{
30+
return 2;
31+
}
32+
33+
public function foo(): void
34+
{
35+
$clUnioned = mt_rand() === 0
36+
? A::class
37+
: X::class;
38+
39+
assertType('int', A::retStaticConst());
40+
assertType('bool', X::retStaticConst());
41+
assertType('bool|int', $clUnioned::retStaticConst());
42+
43+
assertType('Bug11687\A', A::retStatic());
44+
assertType('bool', X::retStatic());
45+
assertType('bool|Bug11687\A', $clUnioned::retStatic());
46+
}
47+
}
48+
49+
class X
50+
{
51+
public static function retStaticConst(): bool
52+
{
53+
return false;
54+
}
55+
56+
public static function retStatic(): bool
57+
{
58+
return false;
59+
}
60+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php // lint >= 8.0
2+
3+
namespace Bug12562;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class UserV1
8+
{
9+
public function toV2(): UserV2
10+
{
11+
return new UserV2();
12+
}
13+
}
14+
15+
class UserV2
16+
{
17+
public function toV2(): self
18+
{
19+
return $this;
20+
}
21+
}
22+
23+
class UserV2a extends UserV2
24+
{
25+
/**
26+
* @return $this
27+
*/
28+
public function toV2(): self
29+
{
30+
return $this;
31+
}
32+
}
33+
34+
function doSomething(UserV1|UserV2 $user, UserV1|UserV2a $user2): void
35+
{
36+
assertType('Bug12562\UserV2', $user->toV2());
37+
assertType('Bug12562\UserV2', $user2->toV2());
38+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php // lint >= 8.1
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug14203;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
/**
10+
* @template TKey of array-key
11+
* @template TValue
12+
*/
13+
class Collection
14+
{
15+
/**
16+
* Create a new collection.
17+
*
18+
* @param array<TKey, TValue> $items
19+
*/
20+
final public function __construct(protected $items = [])
21+
{
22+
}
23+
24+
/**
25+
* @template TMapValue
26+
*
27+
* @param callable(TValue, TKey): TMapValue $callback
28+
* @return static<TKey, TMapValue>
29+
*/
30+
public function map(callable $callback)
31+
{
32+
$newItems = [];
33+
34+
foreach ($this->items as $key => $value) {
35+
$newItems[$key] = $callback($value, $key);
36+
}
37+
38+
return new static($newItems);
39+
}
40+
}
41+
42+
class SpecificA {
43+
public function __construct(
44+
public readonly int $valueA,
45+
public readonly string $someSharedValue,
46+
) {}
47+
}
48+
49+
class SpecificB {
50+
public function __construct(
51+
public readonly int $valueB,
52+
public readonly string $someSharedValue,
53+
) {}
54+
}
55+
56+
class MyDTO {
57+
public function __construct(
58+
public readonly string $thatSharedValue,
59+
) {}
60+
}
61+
62+
function works(): void {
63+
$myCollection = new Collection([new SpecificA(1, 'A'), new SpecificB(2, 'B')]);
64+
65+
$result = $myCollection->map(static fn (SpecificA|SpecificB $specific): MyDTO => new MyDTO($specific->someSharedValue));
66+
assertType('Bug14203\Collection<int, Bug14203\MyDTO>', $result);
67+
}
68+
69+
/**
70+
* @return Collection<int, SpecificA>
71+
*/
72+
function getA(): Collection {
73+
return new Collection([new SpecificA(1, 'A')]);
74+
}
75+
76+
/**
77+
* @return Collection<int, SpecificB>
78+
*/
79+
function getB(): Collection {
80+
return new Collection([new SpecificB(2, 'B')]);
81+
}
82+
83+
function breaks(): void {
84+
$myCollection = random_int(0, 1) === 0
85+
? getA()
86+
: getB();
87+
88+
$result = $myCollection->map(static fn (SpecificA|SpecificB $specific): MyDTO => new MyDTO($specific->someSharedValue));
89+
assertType('Bug14203\Collection<int, Bug14203\MyDTO>', $result);
90+
}

tests/PHPStan/Analyser/nsrt/static-late-binding.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public function foo(): void
8585
assertType('static(StaticLateBinding\B)', parent::retStatic());
8686
assertType('static(StaticLateBinding\B)', $this->retStatic());
8787
assertType('bool', X::retStatic());
88-
assertType('bool|StaticLateBinding\A|StaticLateBinding\X', $clUnioned::retStatic()); // should be bool|StaticLateBinding\A https://github.com/phpstan/phpstan/issues/11687
88+
assertType('bool|StaticLateBinding\A', $clUnioned::retStatic());
8989

9090
assertType('StaticLateBinding\A', A::retStatic(...)());
9191
assertType('StaticLateBinding\B', B::retStatic(...)());

0 commit comments

Comments
 (0)