1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use Closure;
6: use PhpParser\Parser;
7: use PHPStan\BetterReflection\Identifier\IdentifierType;
8: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter;
9: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionType;
10: use PHPStan\BetterReflection\Reflection\ReflectionParameter as BetterReflectionParameter;
11: use PHPStan\BetterReflection\Reflector\Reflector;
12: use PHPStan\BetterReflection\SourceLocator\Ast\FindReflectionsInTree;
13: use PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection;
14: use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource;
15: use PHPStan\BetterReflection\SourceLocator\SourceStubber\ReflectionSourceStubber;
16: use PHPStan\DependencyInjection\AutowiredParameter;
17: use PHPStan\DependencyInjection\AutowiredService;
18: use PHPStan\Reflection\InitializerExprContext;
19: use PHPStan\Reflection\InitializerExprTypeResolver;
20: use PHPStan\Reflection\ParameterReflection;
21: use PHPStan\Reflection\PassedByReference;
22: use PHPStan\ShouldNotHappenException;
23: use ReflectionFunction;
24: use function array_map;
25: use function count;
26: use function str_replace;
27:
28: /**
29: * @api
30: */
31: #[AutowiredService]
32: final class ClosureTypeFactory
33: {
34:
35: public function __construct(
36: private InitializerExprTypeResolver $initializerExprTypeResolver,
37: private ReflectionSourceStubber $reflectionSourceStubber,
38: private Reflector $reflector,
39: #[AutowiredParameter(ref: '@currentPhpVersionPhpParser')]
40: private Parser $parser,
41: )
42: {
43: }
44:
45: /**
46: * @param Closure(): mixed $closure
47: */
48: public function fromClosureObject(Closure $closure): ClosureType
49: {
50: $stubData = $this->reflectionSourceStubber->generateFunctionStubFromReflection(new ReflectionFunction($closure));
51: if ($stubData === null) {
52: throw new ShouldNotHappenException('Closure reflection not found.');
53: }
54: $source = $stubData->getStub();
55: $source = str_replace('{closure}', 'foo', $source);
56: $locatedSource = new LocatedSource($source, '{closure}', $stubData->getFileName());
57: $find = new FindReflectionsInTree(new NodeToReflection());
58: $ast = $this->parser->parse($locatedSource->getSource());
59: if ($ast === null) {
60: throw new ShouldNotHappenException('Closure reflection not found.');
61: }
62:
63: /** @var list<\PHPStan\BetterReflection\Reflection\ReflectionFunction> $reflections */
64: $reflections = $find($this->reflector, $ast, new IdentifierType(IdentifierType::IDENTIFIER_FUNCTION), $locatedSource);
65: if (count($reflections) !== 1) {
66: throw new ShouldNotHappenException('Closure reflection not found.');
67: }
68:
69: $betterReflectionFunction = $reflections[0];
70:
71: $parameters = array_map(fn (BetterReflectionParameter $parameter) => new class($parameter, $this->initializerExprTypeResolver) implements ParameterReflection {
72:
73: public function __construct(private BetterReflectionParameter $reflection, private InitializerExprTypeResolver $initializerExprTypeResolver)
74: {
75: }
76:
77: public function getName(): string
78: {
79: return $this->reflection->getName();
80: }
81:
82: public function isOptional(): bool
83: {
84: return $this->reflection->isOptional();
85: }
86:
87: public function getType(): Type
88: {
89: return TypehintHelper::decideTypeFromReflection(ReflectionType::fromTypeOrNull($this->reflection->getType()), isVariadic: $this->reflection->isVariadic());
90: }
91:
92: public function passedByReference(): PassedByReference
93: {
94: return $this->reflection->isPassedByReference()
95: ? PassedByReference::createCreatesNewVariable()
96: : PassedByReference::createNo();
97: }
98:
99: public function isVariadic(): bool
100: {
101: return $this->reflection->isVariadic();
102: }
103:
104: public function getDefaultValue(): ?Type
105: {
106: if (! $this->reflection->isDefaultValueAvailable()) {
107: return null;
108: }
109:
110: $defaultExpr = $this->reflection->getDefaultValueExpression();
111: if ($defaultExpr === null) {
112: return null;
113: }
114:
115: return $this->initializerExprTypeResolver->getType($defaultExpr, InitializerExprContext::fromReflectionParameter(new ReflectionParameter($this->reflection)));
116: }
117:
118: }, $betterReflectionFunction->getParameters());
119:
120: return new ClosureType($parameters, TypehintHelper::decideTypeFromReflection(ReflectionType::fromTypeOrNull($betterReflectionFunction->getReturnType())), $betterReflectionFunction->isVariadic());
121: }
122:
123: }
124: