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