Skip to content

Commit f81fc8a

Browse files
committed
resolveType for first class callable handlers
1 parent 5fa2f28 commit f81fc8a

File tree

5 files changed

+273
-39
lines changed

5 files changed

+273
-39
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\ExprHandler;
4+
5+
use Closure;
6+
use PhpParser\Node\Expr;
7+
use PhpParser\Node\Expr\FuncCall;
8+
use PhpParser\Node\Stmt;
9+
use PHPStan\Analyser\ExpressionContext;
10+
use PHPStan\Analyser\ExpressionResult;
11+
use PHPStan\Analyser\ExpressionResultStorage;
12+
use PHPStan\Analyser\ExprHandler;
13+
use PHPStan\Analyser\MutatingScope;
14+
use PHPStan\Analyser\NodeScopeResolver;
15+
use PHPStan\DependencyInjection\AutowiredService;
16+
use PHPStan\Reflection\InitializerExprContext;
17+
use PHPStan\Reflection\InitializerExprTypeResolver;
18+
use PHPStan\ShouldNotHappenException;
19+
use PHPStan\Type\ObjectType;
20+
use PHPStan\Type\Type;
21+
22+
/**
23+
* @implements ExprHandler<FuncCall>
24+
*/
25+
#[AutowiredService]
26+
final class FirstClassCallableFuncCallHandler implements ExprHandler
27+
{
28+
29+
public function __construct(
30+
private InitializerExprTypeResolver $initializerExprTypeResolver,
31+
)
32+
{
33+
}
34+
35+
public function supports(Expr $expr): bool
36+
{
37+
return $expr instanceof FuncCall && $expr->isFirstClassCallable();
38+
}
39+
40+
public function processExpr(
41+
NodeScopeResolver $nodeScopeResolver,
42+
Stmt $stmt,
43+
Expr $expr,
44+
MutatingScope $scope,
45+
ExpressionResultStorage $storage,
46+
callable $nodeCallback,
47+
ExpressionContext $context,
48+
): ExpressionResult
49+
{
50+
// handled in NodeScopeResolver before ExprHandlers are called
51+
throw new ShouldNotHappenException();
52+
}
53+
54+
/**
55+
* @param FuncCall $expr
56+
*/
57+
public function resolveType(MutatingScope $scope, Expr $expr): Type
58+
{
59+
if ($expr->name instanceof Expr) {
60+
$callableType = $scope->getType($expr->name);
61+
if (!$callableType->isCallable()->yes()) {
62+
return new ObjectType(Closure::class);
63+
}
64+
65+
return $this->initializerExprTypeResolver->createFirstClassCallable(
66+
null,
67+
$callableType->getCallableParametersAcceptors($scope),
68+
$scope->nativeTypesPromoted,
69+
);
70+
}
71+
72+
return $this->initializerExprTypeResolver->getFirstClassCallableType($expr, InitializerExprContext::fromScope($scope), $scope->nativeTypesPromoted);
73+
}
74+
75+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\ExprHandler;
4+
5+
use Closure;
6+
use PhpParser\Node\Expr;
7+
use PhpParser\Node\Expr\MethodCall;
8+
use PhpParser\Node\Identifier;
9+
use PhpParser\Node\Stmt;
10+
use PHPStan\Analyser\ExpressionContext;
11+
use PHPStan\Analyser\ExpressionResult;
12+
use PHPStan\Analyser\ExpressionResultStorage;
13+
use PHPStan\Analyser\ExprHandler;
14+
use PHPStan\Analyser\MutatingScope;
15+
use PHPStan\Analyser\NodeScopeResolver;
16+
use PHPStan\DependencyInjection\AutowiredService;
17+
use PHPStan\Reflection\InitializerExprTypeResolver;
18+
use PHPStan\ShouldNotHappenException;
19+
use PHPStan\Type\ObjectType;
20+
use PHPStan\Type\Type;
21+
22+
/**
23+
* @implements ExprHandler<MethodCall>
24+
*/
25+
#[AutowiredService]
26+
final class FirstClassCallableMethodCallHandler implements ExprHandler
27+
{
28+
29+
public function __construct(
30+
private InitializerExprTypeResolver $initializerExprTypeResolver,
31+
)
32+
{
33+
}
34+
35+
public function supports(Expr $expr): bool
36+
{
37+
return $expr instanceof MethodCall && $expr->isFirstClassCallable();
38+
}
39+
40+
public function processExpr(
41+
NodeScopeResolver $nodeScopeResolver,
42+
Stmt $stmt,
43+
Expr $expr,
44+
MutatingScope $scope,
45+
ExpressionResultStorage $storage,
46+
callable $nodeCallback,
47+
ExpressionContext $context,
48+
): ExpressionResult
49+
{
50+
// handled in NodeScopeResolver before ExprHandlers are called
51+
throw new ShouldNotHappenException();
52+
}
53+
54+
/**
55+
* @param MethodCall $expr
56+
*/
57+
public function resolveType(MutatingScope $scope, Expr $expr): Type
58+
{
59+
if (!$expr->name instanceof Identifier) {
60+
return new ObjectType(Closure::class);
61+
}
62+
63+
$varType = $scope->getType($expr->var);
64+
$method = $scope->getMethodReflection($varType, $expr->name->toString());
65+
if ($method === null) {
66+
return new ObjectType(Closure::class);
67+
}
68+
69+
return $this->initializerExprTypeResolver->createFirstClassCallable(
70+
$method,
71+
$method->getVariants(),
72+
$scope->nativeTypesPromoted,
73+
);
74+
}
75+
76+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\ExprHandler;
4+
5+
use PhpParser\Node\Expr;
6+
use PhpParser\Node\Expr\New_;
7+
use PhpParser\Node\Stmt;
8+
use PhpParser\Node\Stmt\Class_;
9+
use PHPStan\Analyser\ExpressionContext;
10+
use PHPStan\Analyser\ExpressionResult;
11+
use PHPStan\Analyser\ExpressionResultStorage;
12+
use PHPStan\Analyser\ExprHandler;
13+
use PHPStan\Analyser\MutatingScope;
14+
use PHPStan\Analyser\NodeScopeResolver;
15+
use PHPStan\DependencyInjection\AutowiredService;
16+
use PHPStan\Reflection\InitializerExprContext;
17+
use PHPStan\Reflection\InitializerExprTypeResolver;
18+
use PHPStan\ShouldNotHappenException;
19+
use PHPStan\Type\Type;
20+
21+
/**
22+
* @implements ExprHandler<New_>
23+
*/
24+
#[AutowiredService]
25+
final class FirstClassCallableNewHandler implements ExprHandler
26+
{
27+
28+
public function __construct(
29+
private InitializerExprTypeResolver $initializerExprTypeResolver,
30+
)
31+
{
32+
}
33+
34+
public function supports(Expr $expr): bool
35+
{
36+
return $expr instanceof New_ && !$expr->class instanceof Class_ && $expr->isFirstClassCallable();
37+
}
38+
39+
public function processExpr(
40+
NodeScopeResolver $nodeScopeResolver,
41+
Stmt $stmt,
42+
Expr $expr,
43+
MutatingScope $scope,
44+
ExpressionResultStorage $storage,
45+
callable $nodeCallback,
46+
ExpressionContext $context,
47+
): ExpressionResult
48+
{
49+
// handled in NodeScopeResolver before ExprHandlers are called
50+
throw new ShouldNotHappenException();
51+
}
52+
53+
/**
54+
* @param New_ $expr
55+
*/
56+
public function resolveType(MutatingScope $scope, Expr $expr): Type
57+
{
58+
return $this->initializerExprTypeResolver->getFirstClassCallableType($expr, InitializerExprContext::fromScope($scope), $scope->nativeTypesPromoted);
59+
}
60+
61+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\ExprHandler;
4+
5+
use PhpParser\Node\Expr;
6+
use PhpParser\Node\Expr\StaticCall;
7+
use PhpParser\Node\Stmt;
8+
use PHPStan\Analyser\ExpressionContext;
9+
use PHPStan\Analyser\ExpressionResult;
10+
use PHPStan\Analyser\ExpressionResultStorage;
11+
use PHPStan\Analyser\ExprHandler;
12+
use PHPStan\Analyser\MutatingScope;
13+
use PHPStan\Analyser\NodeScopeResolver;
14+
use PHPStan\DependencyInjection\AutowiredService;
15+
use PHPStan\Reflection\InitializerExprContext;
16+
use PHPStan\Reflection\InitializerExprTypeResolver;
17+
use PHPStan\ShouldNotHappenException;
18+
use PHPStan\Type\Type;
19+
20+
/**
21+
* @implements ExprHandler<StaticCall>
22+
*/
23+
#[AutowiredService]
24+
final class FirstClassCallableStaticCallHandler implements ExprHandler
25+
{
26+
27+
public function __construct(
28+
private InitializerExprTypeResolver $initializerExprTypeResolver,
29+
)
30+
{
31+
}
32+
33+
public function supports(Expr $expr): bool
34+
{
35+
return $expr instanceof StaticCall && $expr->isFirstClassCallable();
36+
}
37+
38+
public function processExpr(
39+
NodeScopeResolver $nodeScopeResolver,
40+
Stmt $stmt,
41+
Expr $expr,
42+
MutatingScope $scope,
43+
ExpressionResultStorage $storage,
44+
callable $nodeCallback,
45+
ExpressionContext $context,
46+
): ExpressionResult
47+
{
48+
// handled in NodeScopeResolver before ExprHandlers are called
49+
throw new ShouldNotHappenException();
50+
}
51+
52+
/**
53+
* @param StaticCall $expr
54+
*/
55+
public function resolveType(MutatingScope $scope, Expr $expr): Type
56+
{
57+
return $this->initializerExprTypeResolver->getFirstClassCallableType($expr, InitializerExprContext::fromScope($scope), $scope->nativeTypesPromoted);
58+
}
59+
60+
}

src/Analyser/MutatingScope.php

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace PHPStan\Analyser;
44

5-
use Closure;
65
use PhpParser\Node;
76
use PhpParser\Node\Arg;
87
use PhpParser\Node\ComplexType;
@@ -1072,9 +1071,7 @@ private function resolveType(string $exprString, Expr $node): Type
10721071
return $this->nativeTypesPromoted ? $node->getNativeExprType() : $node->getExprType();
10731072
}
10741073

1075-
if ($node instanceof Expr\CallLike && $node->isFirstClassCallable()) {
1076-
return $this->getFirstClassCallableType($node);
1077-
} elseif ($node instanceof Unset_) {
1074+
if ($node instanceof Unset_) {
10781075
return new NullType();
10791076
}
10801077

@@ -4853,39 +4850,4 @@ private function getMethodCallType(MethodCall $node): ?Type
48534850
return null;
48544851
}
48554852

4856-
private function getFirstClassCallableType(Expr\CallLike $node): Type
4857-
{
4858-
if ($node instanceof FuncCall && $node->name instanceof Expr) {
4859-
$callableType = $this->getType($node->name);
4860-
if (!$callableType->isCallable()->yes()) {
4861-
return new ObjectType(Closure::class);
4862-
}
4863-
4864-
return $this->initializerExprTypeResolver->createFirstClassCallable(
4865-
null,
4866-
$callableType->getCallableParametersAcceptors($this),
4867-
$this->nativeTypesPromoted,
4868-
);
4869-
}
4870-
if ($node instanceof MethodCall) {
4871-
if (!$node->name instanceof Node\Identifier) {
4872-
return new ObjectType(Closure::class);
4873-
}
4874-
4875-
$varType = $this->getType($node->var);
4876-
$method = $this->getMethodReflection($varType, $node->name->toString());
4877-
if ($method === null) {
4878-
return new ObjectType(Closure::class);
4879-
}
4880-
4881-
return $this->initializerExprTypeResolver->createFirstClassCallable(
4882-
$method,
4883-
$method->getVariants(),
4884-
$this->nativeTypesPromoted,
4885-
);
4886-
}
4887-
4888-
return $this->initializerExprTypeResolver->getFirstClassCallableType($node, InitializerExprContext::fromScope($this), $this->nativeTypesPromoted);
4889-
}
4890-
48914853
}

0 commit comments

Comments
 (0)