148148use function is_numeric ;
149149use function is_string ;
150150use function ltrim ;
151+ use function md5 ;
151152use function sprintf ;
152153use function str_starts_with ;
153154use function strlen ;
@@ -702,6 +703,30 @@ private function getNodeKey(Expr $node): string
702703 return $ key ;
703704 }
704705
706+ private function getClosureScopeCacheKey (): string
707+ {
708+ $ parts = [];
709+ foreach ($ this ->expressionTypes as $ exprString => $ expressionTypeHolder ) {
710+ $ parts [] = sprintf ('%s::%s ' , $ exprString , $ expressionTypeHolder ->getType ()->describe (VerbosityLevel::cache ()));
711+ }
712+ $ parts [] = '--- ' ;
713+ foreach ($ this ->nativeExpressionTypes as $ exprString => $ expressionTypeHolder ) {
714+ $ parts [] = sprintf ('%s::%s ' , $ exprString , $ expressionTypeHolder ->getType ()->describe (VerbosityLevel::cache ()));
715+ }
716+
717+ $ parts [] = sprintf (':%d ' , count ($ this ->inFunctionCallsStack ));
718+ foreach ($ this ->inFunctionCallsStack as [$ method , $ parameter ]) {
719+ if ($ parameter === null ) {
720+ $ parts [] = ',null ' ;
721+ continue ;
722+ }
723+
724+ $ parts [] = sprintf (',%s ' , $ parameter ->getType ()->describe (VerbosityLevel::cache ()));
725+ }
726+
727+ return md5 (implode ("\n" , $ parts ));
728+ }
729+
705730 private function resolveType (string $ exprString , Expr $ node ): Type
706731 {
707732 foreach ($ this ->expressionTypeResolverExtensionRegistry ->getExtensions () as $ extension ) {
@@ -1318,6 +1343,25 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu
13181343 $ impurePoints = array_merge ($ arrowFunctionImpurePoints , $ arrowFunctionExprResult ->getImpurePoints ());
13191344 $ usedVariables = [];
13201345 } else {
1346+ $ cachedTypes = $ node ->getAttribute ('phpstanCachedTypes ' , []);
1347+ $ cacheKey = $ this ->getClosureScopeCacheKey ();
1348+ if (array_key_exists ($ cacheKey , $ cachedTypes )) {
1349+ $ cachedClosureData = $ cachedTypes [$ cacheKey ];
1350+
1351+ return new ClosureType (
1352+ $ parameters ,
1353+ $ cachedClosureData ['returnType ' ],
1354+ $ isVariadic ,
1355+ TemplateTypeMap::createEmpty (),
1356+ TemplateTypeMap::createEmpty (),
1357+ TemplateTypeVarianceMap::createEmpty (),
1358+ [],
1359+ $ cachedClosureData ['throwPoints ' ],
1360+ $ cachedClosureData ['impurePoints ' ],
1361+ $ cachedClosureData ['invalidateExpressions ' ],
1362+ $ cachedClosureData ['usedVariables ' ],
1363+ );
1364+ }
13211365 if (self ::$ resolveClosureTypeDepth >= 2 ) {
13221366 return new ClosureType (
13231367 $ parameters ,
@@ -1483,6 +1527,19 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu
14831527 }
14841528 }
14851529
1530+ $ throwPointsForClosureType = array_map (static fn (ThrowPoint $ throwPoint ) => $ throwPoint ->isExplicit () ? SimpleThrowPoint::createExplicit ($ throwPoint ->getType (), $ throwPoint ->canContainAnyThrowable ()) : SimpleThrowPoint::createImplicit (), $ throwPoints );
1531+ $ impurePointsForClosureType = array_map (static fn (ImpurePoint $ impurePoint ) => new SimpleImpurePoint ($ impurePoint ->getIdentifier (), $ impurePoint ->getDescription (), $ impurePoint ->isCertain ()), $ impurePoints );
1532+
1533+ $ cachedTypes = $ node ->getAttribute ('phpstanCachedTypes ' , []);
1534+ $ cachedTypes [$ this ->getClosureScopeCacheKey ()] = [
1535+ 'returnType ' => $ returnType ,
1536+ 'throwPoints ' => $ throwPointsForClosureType ,
1537+ 'impurePoints ' => $ impurePointsForClosureType ,
1538+ 'invalidateExpressions ' => $ invalidateExpressions ,
1539+ 'usedVariables ' => $ usedVariables ,
1540+ ];
1541+ $ node ->setAttribute ('phpstanCachedTypes ' , $ cachedTypes );
1542+
14861543 return new ClosureType (
14871544 $ parameters ,
14881545 $ returnType ,
@@ -1491,8 +1548,8 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu
14911548 TemplateTypeMap::createEmpty (),
14921549 TemplateTypeVarianceMap::createEmpty (),
14931550 [],
1494- array_map ( static fn ( ThrowPoint $ throwPoint ) => $ throwPoint -> isExplicit () ? SimpleThrowPoint:: createExplicit ( $ throwPoint -> getType (), $ throwPoint -> canContainAnyThrowable ()) : SimpleThrowPoint:: createImplicit (), $ throwPoints ) ,
1495- array_map ( static fn ( ImpurePoint $ impurePoint ) => new SimpleImpurePoint ( $ impurePoint -> getIdentifier (), $ impurePoint -> getDescription (), $ impurePoint -> isCertain ()), $ impurePoints ) ,
1551+ $ throwPointsForClosureType ,
1552+ $ impurePointsForClosureType ,
14961553 $ invalidateExpressions ,
14971554 $ usedVariables ,
14981555 );
0 commit comments