Skip to content

Commit c5d7128

Browse files
committed
Prevent infinite recursion when describing template type with default value
1 parent 6bf3ce7 commit c5d7128

File tree

2 files changed

+31
-1
lines changed

2 files changed

+31
-1
lines changed

src/Type/Generic/TemplateTypeTrait.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
77
use PHPStan\TrinaryLogic;
88
use PHPStan\Type\AcceptsResult;
9+
use PHPStan\Type\ErrorType;
910
use PHPStan\Type\IntersectionType;
1011
use PHPStan\Type\IsSuperTypeOfResult;
1112
use PHPStan\Type\MixedType;
1213
use PHPStan\Type\NeverType;
14+
use PHPStan\Type\RecursionGuard;
1315
use PHPStan\Type\SubtractableType;
1416
use PHPStan\Type\Type;
1517
use PHPStan\Type\TypeCombinator;
@@ -69,7 +71,13 @@ public function describe(VerbosityLevel $level): string
6971
} else {
7072
$boundDescription = sprintf(' of %s', $this->bound->describe($level));
7173
}
72-
$defaultDescription = $this->default !== null ? sprintf(' = %s', $this->default->describe($level)) : '';
74+
$defaultDescription = '';
75+
if ($this->default !== null) {
76+
$recursionGuard = RecursionGuard::runOnObjectIdentity($this->default, fn () => $this->default->describe($level));
77+
if (!$recursionGuard instanceof ErrorType) {
78+
$defaultDescription .= sprintf(' = %s', $recursionGuard);
79+
}
80+
}
7381
return sprintf(
7482
'%s%s%s',
7583
$this->name,

src/Type/RecursionGuard.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace PHPStan\Type;
44

5+
use function spl_object_hash;
6+
57
final class RecursionGuard
68
{
79

@@ -28,4 +30,24 @@ public static function run(Type $type, callable $callback)
2830
}
2931
}
3032

33+
/**
34+
* @template T
35+
* @param callable(): T $callback
36+
* @return T|ErrorType
37+
*/
38+
public static function runOnObjectIdentity(Type $type, callable $callback)
39+
{
40+
$key = spl_object_hash($type);
41+
if (isset(self::$context[$key])) {
42+
return new ErrorType();
43+
}
44+
45+
try {
46+
self::$context[$key] = true;
47+
return $callback();
48+
} finally {
49+
unset(self::$context[$key]);
50+
}
51+
}
52+
3153
}

0 commit comments

Comments
 (0)