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