Skip to content

Commit c9fc9ee

Browse files
committed
Merge branch 2.1.x into 2.2.x
2 parents 6aa6db5 + 72b1bb5 commit c9fc9ee

File tree

5 files changed

+164
-0
lines changed

5 files changed

+164
-0
lines changed

src/Type/Generic/TemplateTypeTrait.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,10 @@ public function traverseSimultaneously(Type $right, callable $cb): Type
368368

369369
public function tryRemove(Type $typeToRemove): ?Type
370370
{
371+
if ($typeToRemove instanceof TemplateType) {
372+
return null;
373+
}
374+
371375
$bound = TypeCombinator::remove($this->getBound(), $typeToRemove);
372376
if ($this->getBound() === $bound) {
373377
return null;
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug11430Methods;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @template T
9+
*
10+
* @implements \IteratorAggregate<T>
11+
*/
12+
abstract class Option implements \IteratorAggregate
13+
{
14+
/**
15+
* @template S
16+
* @template U
17+
*
18+
* @param S $value The actual return value.
19+
* @param U $noneValue The value which should be considered "None"; null by
20+
* default.
21+
*
22+
* @return (S is U ? None : Option<S>)
23+
*/
24+
public static function fromValue($value, $noneValue = null)
25+
{
26+
if ($value === $noneValue) {
27+
return None::create();
28+
}
29+
30+
return new Some($value);
31+
}
32+
}
33+
34+
/**
35+
* @extends Option<mixed>
36+
*/
37+
final class None extends Option
38+
{
39+
/** @var None|null */
40+
private static $instance;
41+
42+
/**
43+
* @return None
44+
*/
45+
public static function create(): self
46+
{
47+
if (null === self::$instance) {
48+
self::$instance = new self();
49+
}
50+
51+
return self::$instance;
52+
}
53+
54+
public function getIterator(): \EmptyIterator
55+
{
56+
return new \EmptyIterator();
57+
}
58+
}
59+
60+
/**
61+
* @template T
62+
*
63+
* @extends Option<T>
64+
*/
65+
final class Some extends Option
66+
{
67+
/** @var T */
68+
private $value;
69+
70+
/**
71+
* @param T $value
72+
*/
73+
public function __construct($value)
74+
{
75+
$this->value = $value;
76+
}
77+
78+
/**
79+
* @return \ArrayIterator<int, T>
80+
*/
81+
public function getIterator(): \ArrayIterator
82+
{
83+
return new \ArrayIterator([$this->value]);
84+
}
85+
}
86+
87+
88+
class Test
89+
{
90+
/** @var Option<string> */
91+
public Option $name;
92+
}
93+
94+
$test = new Test();
95+
/** @var ?string $foo */
96+
$foo = null;
97+
$test->name = Option::fromValue($foo);
98+
assertType('Bug11430Methods\Option<string>', $test->name);

tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,4 +1326,9 @@ public function testBug10924(): void
13261326
$this->analyse([__DIR__ . '/data/bug-10924.php'], []);
13271327
}
13281328

1329+
public function testBug11430(): void
1330+
{
1331+
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-11430.php'], []);
1332+
}
1333+
13291334
}

tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,4 +309,10 @@ public function testBug13676(): void
309309
$this->analyse([__DIR__ . '/data/bug-13676.php'], []);
310310
}
311311

312+
#[RequiresPhp('>= 8.2')]
313+
public function testBug11430(): void
314+
{
315+
$this->analyse([__DIR__ . '/data/bug-11430.php'], []);
316+
}
317+
312318
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php // lint >= 8.2
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug11430;
6+
7+
/**
8+
* @template T
9+
*/
10+
interface Option {}
11+
12+
/**
13+
* @template T
14+
* @implements Option<T>
15+
*/
16+
class Some implements Option
17+
{
18+
/**
19+
* @param T $value
20+
*/
21+
public function __construct(public mixed $value) {}
22+
}
23+
24+
/**
25+
* @implements Option<never>
26+
*/
27+
class None implements Option {}
28+
29+
/**
30+
* @internal
31+
*/
32+
final class Choice
33+
{
34+
/**
35+
* @template T
36+
* @template S
37+
*
38+
* @param T $value
39+
* @param S $none
40+
*
41+
* @return (T is S ? None : Some<T>)
42+
*/
43+
public static function from(mixed $value, mixed $none = null): Option
44+
{
45+
if ($value === $none) {
46+
return new None();
47+
}
48+
49+
return new Some($value);
50+
}
51+
}

0 commit comments

Comments
 (0)