3636use PHPStan \Type \Generic \TemplateTypeHelper ;
3737use PHPStan \Type \Generic \TemplateTypeMap ;
3838use PHPStan \Type \Generic \TemplateTypeScope ;
39+ use PHPStan \Type \Generic \TemplateTypeVariance ;
40+ use PHPStan \Type \Generic \TemplateTypeVarianceMap ;
41+ use PHPStan \Type \Generic \TypeProjectionHelper ;
3942use PHPStan \Type \Type ;
4043use PHPStan \Type \TypeAlias ;
4144use PHPStan \Type \TypehintHelper ;
@@ -94,6 +97,10 @@ class ClassReflection
9497
9598private ?TemplateTypeMap $ activeTemplateTypeMap = null ;
9699
100+ private ?TemplateTypeVarianceMap $ defaultCallSiteVarianceMap = null ;
101+
102+ private ?TemplateTypeVarianceMap $ callSiteVarianceMap = null ;
103+
97104/** @var array<string,ClassReflection>|null */
98105private ?array $ ancestors = null ;
99106
@@ -140,6 +147,7 @@ public function __construct(
140147private ?TemplateTypeMap $ resolvedTemplateTypeMap ,
141148private ?ResolvedPhpDocBlock $ stubPhpDocBlock ,
142149private ?string $ extraCacheKey = null ,
150+ private ?TemplateTypeVarianceMap $ resolvedCallSiteVarianceMap = null ,
143151)
144152{
145153}
@@ -241,7 +249,11 @@ public function getDisplayName(bool $withTemplateTypes = true): string
241249return $ name ;
242250}
243251
244- return $ name . '< ' . implode (', ' , array_map (static fn (Type $ type ): string => $ type ->describe (VerbosityLevel::typeOnly ()), $ this ->getActiveTemplateTypeMap ()->getTypes ())) . '> ' ;
252+ return $ name . '< ' . implode (', ' , array_map (
253+ static fn (Type $ type , TemplateTypeVariance $ variance ): string => TypeProjectionHelper::describe ($ type , $ variance , VerbosityLevel::typeOnly ()),
254+ $ this ->getActiveTemplateTypeMap ()->getTypes (),
255+ $ this ->getCallSiteVarianceMap ()->getVariances (),
256+ )) . '> ' ;
245257}
246258
247259public function getCacheKey (): string
@@ -254,7 +266,11 @@ public function getCacheKey(): string
254266$ cacheKey = $ this ->displayName ;
255267
256268if ($ this ->resolvedTemplateTypeMap !== null ) {
257- $ cacheKey .= '< ' . implode (', ' , array_map (static fn (Type $ type ): string => $ type ->describe (VerbosityLevel::cache ()), $ this ->resolvedTemplateTypeMap ->getTypes ())) . '> ' ;
269+ $ cacheKey .= '< ' . implode (', ' , array_map (
270+ static fn (Type $ type , TemplateTypeVariance $ variance ): string => TypeProjectionHelper::describe ($ type , $ variance , VerbosityLevel::cache ()),
271+ $ this ->getActiveTemplateTypeMap ()->getTypes (),
272+ $ this ->getCallSiteVarianceMap ()->getVariances (),
273+ )) . '> ' ;
258274}
259275
260276if ($ this ->extraCacheKey !== null ) {
@@ -1239,6 +1255,32 @@ public function getPossiblyIncompleteActiveTemplateTypeMap(): TemplateTypeMap
12391255return $ this ->resolvedTemplateTypeMap ?? $ this ->getTemplateTypeMap ();
12401256}
12411257
1258+ private function getDefaultCallSiteVarianceMap (): TemplateTypeVarianceMap
1259+ {
1260+ if ($ this ->defaultCallSiteVarianceMap !== null ) {
1261+ return $ this ->defaultCallSiteVarianceMap ;
1262+ }
1263+
1264+ $ resolvedPhpDoc = $ this ->getResolvedPhpDoc ();
1265+ if ($ resolvedPhpDoc === null ) {
1266+ $ this ->defaultCallSiteVarianceMap = TemplateTypeVarianceMap::createEmpty ();
1267+ return $ this ->defaultCallSiteVarianceMap ;
1268+ }
1269+
1270+ $ map = [];
1271+ foreach ($ this ->getTemplateTags () as $ templateTag ) {
1272+ $ map [$ templateTag ->getName ()] = TemplateTypeVariance::createInvariant ();
1273+ }
1274+
1275+ $ this ->defaultCallSiteVarianceMap = new TemplateTypeVarianceMap ($ map );
1276+ return $ this ->defaultCallSiteVarianceMap ;
1277+ }
1278+
1279+ public function getCallSiteVarianceMap (): TemplateTypeVarianceMap
1280+ {
1281+ return $ this ->callSiteVarianceMap ??= $ this ->resolvedCallSiteVarianceMap ?? $ this ->getDefaultCallSiteVarianceMap ();
1282+ }
1283+
12421284public function isGeneric (): bool
12431285{
12441286if ($ this ->isGeneric === null ) {
@@ -1272,6 +1314,26 @@ public function typeMapFromList(array $types): TemplateTypeMap
12721314return new TemplateTypeMap ($ map );
12731315}
12741316
1317+ /**
1318+ * @param array<int, TemplateTypeVariance> $variances
1319+ */
1320+ public function varianceMapFromList (array $ variances ): TemplateTypeVarianceMap
1321+ {
1322+ $ resolvedPhpDoc = $ this ->getResolvedPhpDoc ();
1323+ if ($ resolvedPhpDoc === null ) {
1324+ return new TemplateTypeVarianceMap ([]);
1325+ }
1326+
1327+ $ map = [];
1328+ $ i = 0 ;
1329+ foreach ($ resolvedPhpDoc ->getTemplateTags () as $ tag ) {
1330+ $ map [$ tag ->getName ()] = $ variances [$ i ] ?? TemplateTypeVariance::createInvariant ();
1331+ $ i ++;
1332+ }
1333+
1334+ return new TemplateTypeVarianceMap ($ map );
1335+ }
1336+
12751337/** @return array<int, Type> */
12761338public function typeMapToList (TemplateTypeMap $ typeMap ): array
12771339{
@@ -1288,6 +1350,22 @@ public function typeMapToList(TemplateTypeMap $typeMap): array
12881350return $ list ;
12891351}
12901352
1353+ /** @return array<int, TemplateTypeVariance> */
1354+ public function varianceMapToList (TemplateTypeVarianceMap $ varianceMap ): array
1355+ {
1356+ $ resolvedPhpDoc = $ this ->getResolvedPhpDoc ();
1357+ if ($ resolvedPhpDoc === null ) {
1358+ return [];
1359+ }
1360+
1361+ $ list = [];
1362+ foreach ($ resolvedPhpDoc ->getTemplateTags () as $ tag ) {
1363+ $ list [] = $ varianceMap ->getVariance ($ tag ->getName ()) ?? TemplateTypeVariance::createInvariant ();
1364+ }
1365+
1366+ return $ list ;
1367+ }
1368+
12911369/**
12921370 * @param array<int, Type> $types
12931371 */
@@ -1308,6 +1386,33 @@ public function withTypes(array $types): self
13081386$ this ->anonymousFilename ,
13091387$ this ->typeMapFromList ($ types ),
13101388$ this ->stubPhpDocBlock ,
1389+ null ,
1390+ $ this ->resolvedCallSiteVarianceMap ,
1391+ );
1392+ }
1393+
1394+ /**
1395+ * @param array<int, TemplateTypeVariance> $variances
1396+ */
1397+ public function withVariances (array $ variances ): self
1398+ {
1399+ return new self (
1400+ $ this ->reflectionProvider ,
1401+ $ this ->initializerExprTypeResolver ,
1402+ $ this ->fileTypeMapper ,
1403+ $ this ->stubPhpDocProvider ,
1404+ $ this ->phpDocInheritanceResolver ,
1405+ $ this ->phpVersion ,
1406+ $ this ->propertiesClassReflectionExtensions ,
1407+ $ this ->methodsClassReflectionExtensions ,
1408+ $ this ->allowedSubTypesClassReflectionExtensions ,
1409+ $ this ->displayName ,
1410+ $ this ->reflection ,
1411+ $ this ->anonymousFilename ,
1412+ $ this ->resolvedTemplateTypeMap ,
1413+ $ this ->stubPhpDocBlock ,
1414+ null ,
1415+ $ this ->varianceMapFromList ($ variances ),
13111416);
13121417}
13131418
0 commit comments