Skip to content

Commit b0572ce

Browse files
Narrow a class-string with call-site variance by its class name
A class name compared with a class-string of its own generic class now takes the type arguments and the call-site variance of that class-string as they are written: `X::class` read against `class-string<X<*>>` is `X<*>`. So `$class !== X::class` narrows `class-string<X<*>>|class-string<Y<*>>` to `class-string<Y<*>>`. Before, a class-string with call-site variance left the class name bare, which is only maybe an `X<*>`, and nothing narrowed. A subclass still stays bare when the class-string has call-site variance.
1 parent c9a3b33 commit b0572ce

2 files changed

Lines changed: 67 additions & 4 deletions

File tree

‎src/Type/Generic/GenericObjectType.php‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -462,19 +462,32 @@ public function changeSubtractedType(?Type $subtractedType): Type
462462
* Gives $subclass, a class written without type arguments, the ones
463463
* $supertype implies for it through the class's `@extends` and
464464
* `@implements` tags: Some with Option<int> is Some<int>, Err with
465-
* Result<int, string> is Err<string>.
465+
* Result<int, string> is Err<string>. The class of $supertype itself
466+
* takes its arguments and call-site variance as written: X with X<*> is
467+
* X<*>.
466468
*
467469
* Returns $subclass unchanged unless $supertype is a generic object type
468-
* without call-site variance, $subclass is a generic subtype of its class,
469-
* and $supertype determines every type argument of $subclass - an explicit
470-
* argument would claim more than is known.
470+
* and $subclass is its class or a generic subtype of it. A subtype also
471+
* stays unchanged when $supertype has call-site variance or does not
472+
* determine every type argument of the subtype - an explicit argument
473+
* would claim more than is known.
471474
*/
472475
public static function specializeSubclass(Type $supertype, Type $subclass): Type
473476
{
474477
if (!$supertype instanceof self || get_class($subclass) !== ObjectType::class) {
475478
return $subclass;
476479
}
477480

481+
if ($subclass->getClassName() === $supertype->getClassName()) {
482+
return new self(
483+
$supertype->getClassName(),
484+
$supertype->types,
485+
$subclass->getSubtractedType(),
486+
null,
487+
$supertype->variances,
488+
);
489+
}
490+
478491
foreach ($supertype->variances as $variance) {
479492
if (!$variance->invariant()) {
480493
return $subclass;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Bug15266;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/** @template T = mixed */
8+
final class X {}
9+
10+
/** @template T = mixed */
11+
final class Y {}
12+
13+
/**
14+
* @param class-string<X<int>> | class-string<Y<int>> $class
15+
* @return class-string<X<int>> | class-string<Y<int>>
16+
*/
17+
function parametrized(string $class): string
18+
{
19+
if ($class !== X::class) {
20+
assertType('class-string<Bug15266\Y<int>>', $class);
21+
}
22+
23+
return $class;
24+
}
25+
26+
/**
27+
* @param class-string<X<*>> | class-string<Y<*>> $class
28+
* @return class-string<X<*>> | class-string<Y<*>>
29+
*/
30+
function star(string $class): string
31+
{
32+
if ($class !== X::class) {
33+
assertType('class-string<Bug15266\Y<*>>', $class);
34+
}
35+
36+
return $class;
37+
}
38+
39+
/**
40+
* @param class-string<X> | class-string<Y> $class
41+
* @return class-string<X> | class-string<Y>
42+
*/
43+
function raw(string $class): string
44+
{
45+
if ($class !== X::class) {
46+
assertType('class-string<Bug15266\Y>', $class);
47+
}
48+
49+
return $class;
50+
}

0 commit comments

Comments
 (0)