@@ -63,6 +63,8 @@ class CallableType implements CompoundType, CallableParametersAcceptor
6363
6464 private TemplateTypeMap $ resolvedTemplateTypeMap ;
6565
66+ private TrinaryLogic $ isPure ;
67+
6668 /**
6769 * @api
6870 * @param array<int, ParameterReflection>|null $parameters
@@ -75,13 +77,15 @@ public function __construct(
7577 ?TemplateTypeMap $ templateTypeMap = null ,
7678 ?TemplateTypeMap $ resolvedTemplateTypeMap = null ,
7779 private array $ templateTags = [],
80+ ?TrinaryLogic $ isPure = null ,
7881 )
7982 {
8083 $ this ->parameters = $ parameters ?? [];
8184 $ this ->returnType = $ returnType ?? new MixedType ();
8285 $ this ->isCommonCallable = $ parameters === null && $ returnType === null ;
8386 $ this ->templateTypeMap = $ templateTypeMap ?? TemplateTypeMap::createEmpty ();
8487 $ this ->resolvedTemplateTypeMap = $ resolvedTemplateTypeMap ?? TemplateTypeMap::createEmpty ();
88+ $ this ->isPure = $ isPure ?? TrinaryLogic::createMaybe ();
8589 }
8690
8791 /**
@@ -92,6 +96,11 @@ public function getTemplateTags(): array
9296 return $ this ->templateTags ;
9397 }
9498
99+ public function isPure (): TrinaryLogic
100+ {
101+ return $ this ->isPure ;
102+ }
103+
95104 /**
96105 * @return string[]
97106 */
@@ -146,7 +155,7 @@ public function isSuperTypeOf(Type $type): TrinaryLogic
146155 private function isSuperTypeOfInternal (Type $ type , bool $ treatMixedAsAny ): AcceptsResult
147156 {
148157 $ isCallable = new AcceptsResult ($ type ->isCallable (), []);
149- if ($ isCallable ->no () || $ this -> isCommonCallable ) {
158+ if ($ isCallable ->no ()) {
150159 return $ isCallable ;
151160 }
152161
@@ -155,6 +164,19 @@ private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): Accep
155164 $ scope = new OutOfClassScope ();
156165 }
157166
167+ if ($ this ->isCommonCallable ) {
168+ if ($ this ->isPure ()->yes ()) {
169+ $ typePure = TrinaryLogic::createYes ();
170+ foreach ($ type ->getCallableParametersAcceptors ($ scope ) as $ variant ) {
171+ $ typePure = $ typePure ->and ($ variant ->isPure ());
172+ }
173+
174+ return $ isCallable ->and (new AcceptsResult ($ typePure , []));
175+ }
176+
177+ return $ isCallable ;
178+ }
179+
158180 $ variantsResult = null ;
159181 foreach ($ type ->getCallableParametersAcceptors ($ scope ) as $ variant ) {
160182 $ isSuperType = CallableTypeHelper::isParametersAcceptorSuperTypeOf ($ this , $ variant , $ treatMixedAsAny );
@@ -221,6 +243,7 @@ function (): string {
221243 $ this ->templateTypeMap ,
222244 $ this ->resolvedTemplateTypeMap ,
223245 $ this ->templateTags ,
246+ $ this ->isPure ,
224247 );
225248
226249 return $ printer ->print ($ selfWithoutParameterNames ->toPhpDocNode ());
@@ -247,11 +270,16 @@ public function getThrowPoints(): array
247270
248271 public function getImpurePoints (): array
249272 {
273+ $ pure = $ this ->isPure ();
274+ if ($ pure ->yes ()) {
275+ return [];
276+ }
277+
250278 return [
251279 new SimpleImpurePoint (
252280 'functionCall ' ,
253281 'call to a callable ' ,
254- false ,
282+ $ pure -> no () ,
255283 ),
256284 ];
257285 }
@@ -414,6 +442,7 @@ public function traverse(callable $cb): Type
414442 $ this ->templateTypeMap ,
415443 $ this ->resolvedTemplateTypeMap ,
416444 $ this ->templateTags ,
445+ $ this ->isPure ,
417446 );
418447 }
419448
@@ -463,6 +492,7 @@ public function traverseSimultaneously(Type $right, callable $cb): Type
463492 $ this ->templateTypeMap ,
464493 $ this ->resolvedTemplateTypeMap ,
465494 $ this ->templateTags ,
495+ $ this ->isPure ,
466496 );
467497 }
468498
@@ -599,7 +629,7 @@ public function getFiniteTypes(): array
599629 public function toPhpDocNode (): TypeNode
600630 {
601631 if ($ this ->isCommonCallable ) {
602- return new IdentifierTypeNode ('callable ' );
632+ return new IdentifierTypeNode ($ this -> isPure ()-> yes () ? ' pure-callable ' : 'callable ' );
603633 }
604634
605635 $ parameters = [];
@@ -623,7 +653,7 @@ public function toPhpDocNode(): TypeNode
623653 }
624654
625655 return new CallableTypeNode (
626- new IdentifierTypeNode ('callable ' ),
656+ new IdentifierTypeNode ($ this -> isPure -> yes () ? ' pure-callable ' : 'callable ' ),
627657 $ parameters ,
628658 $ this ->returnType ->toPhpDocNode (),
629659 $ templateTags ,
@@ -639,6 +669,10 @@ public static function __set_state(array $properties): Type
639669 (bool ) $ properties ['isCommonCallable ' ] ? null : $ properties ['parameters ' ],
640670 (bool ) $ properties ['isCommonCallable ' ] ? null : $ properties ['returnType ' ],
641671 $ properties ['variadic ' ],
672+ $ properties ['templateTypeMap ' ],
673+ $ properties ['resolvedTemplateTypeMap ' ],
674+ $ properties ['templateTags ' ],
675+ $ properties ['isPure ' ],
642676 );
643677 }
644678
0 commit comments