Skip to content

Commit 5443c1a

Browse files
committed
Almost final ClassLikeHandler, FunctionHandler, ClassMethodHandler
1 parent f4ef9aa commit 5443c1a

File tree

9 files changed

+1147
-19
lines changed

9 files changed

+1147
-19
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\Generator\NodeHandler;
4+
5+
use Generator;
6+
use PhpParser\Node;
7+
use PhpParser\Node\Expr\New_;
8+
use PhpParser\Node\Stmt;
9+
use PHPStan\Analyser\ArgumentsNormalizer;
10+
use PHPStan\Analyser\ExpressionContext;
11+
use PHPStan\Analyser\Generator\ExprAnalysisRequest;
12+
use PHPStan\Analyser\Generator\GeneratorScope;
13+
use PHPStan\Analyser\Generator\NodeCallbackRequest;
14+
use PHPStan\Analyser\Scope;
15+
use PHPStan\DependencyInjection\AutowiredService;
16+
use PHPStan\Reflection\ParametersAcceptorSelector;
17+
use PHPStan\Reflection\ReflectionProvider;
18+
19+
#[AutowiredService]
20+
final class AttrGroupsHandler
21+
{
22+
23+
public function __construct(
24+
private ReflectionProvider $reflectionProvider,
25+
)
26+
{
27+
}
28+
29+
/**
30+
* @param Node\AttributeGroup[] $attrGroups
31+
* @param (callable(Node, Scope, callable(Node, Scope): void): void)|null $alternativeNodeCallback
32+
*/
33+
public function processAttributeGroups(
34+
Stmt $stmt,
35+
array $attrGroups,
36+
GeneratorScope $scope,
37+
?callable $alternativeNodeCallback,
38+
): Generator
39+
{
40+
foreach ($attrGroups as $attrGroup) {
41+
foreach ($attrGroup->attrs as $attr) {
42+
$className = $scope->resolveName($attr->name);
43+
if ($this->reflectionProvider->hasClass($className)) {
44+
$classReflection = $this->reflectionProvider->getClass($className);
45+
if ($classReflection->hasConstructor()) {
46+
$constructorReflection = $classReflection->getConstructor();
47+
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
48+
$scope,
49+
$attr->args,
50+
$constructorReflection->getVariants(),
51+
$constructorReflection->getNamedArgumentsVariants(),
52+
);
53+
$expr = new New_($attr->name, $attr->args);
54+
$expr = ArgumentsNormalizer::reorderNewArguments($parametersAcceptor, $expr) ?? $expr;
55+
$this->processArgs($stmt, $constructorReflection, null, $parametersAcceptor, $expr, $scope, $nodeCallback, ExpressionContext::createDeep());
56+
$nodeCallback($attr, $scope);
57+
continue;
58+
}
59+
}
60+
61+
foreach ($attr->args as $arg) {
62+
yield new ExprAnalysisRequest($stmt, $arg->value, $scope, ExpressionContext::createDeep(), $alternativeNodeCallback);
63+
yield new NodeCallbackRequest($arg, $scope);
64+
}
65+
yield new NodeCallbackRequest($attr, $scope);
66+
}
67+
yield new NodeCallbackRequest($attrGroup, $scope);
68+
}
69+
}
70+
71+
}
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\Generator\NodeHandler;
4+
5+
use PhpParser\Node\PropertyHook;
6+
use PhpParser\Node\Stmt\ClassMethod;
7+
use PhpParser\Node\Stmt\Function_;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\DependencyInjection\AutowiredService;
10+
use PHPStan\Reflection\InitializerExprContext;
11+
use PHPStan\Reflection\InitializerExprTypeResolver;
12+
use function count;
13+
14+
#[AutowiredService]
15+
final class DeprecatedAttributeHelper
16+
{
17+
18+
public function __construct(
19+
private InitializerExprTypeResolver $initializerExprTypeResolver,
20+
)
21+
{
22+
}
23+
24+
/**
25+
* @return array{bool, string|null}
26+
*/
27+
public function getDeprecatedAttribute(Scope $scope, Function_|ClassMethod|PropertyHook $stmt): array
28+
{
29+
$initializerExprContext = InitializerExprContext::fromStubParameter(
30+
$scope->isInClass() ? $scope->getClassReflection()->getName() : null,
31+
$scope->getFile(),
32+
$stmt,
33+
);
34+
$isDeprecated = false;
35+
$deprecatedDescription = null;
36+
$deprecatedDescriptionType = null;
37+
foreach ($stmt->attrGroups as $attrGroup) {
38+
foreach ($attrGroup->attrs as $attr) {
39+
if ($attr->name->toString() !== 'Deprecated') {
40+
continue;
41+
}
42+
$isDeprecated = true;
43+
$arguments = $attr->args;
44+
foreach ($arguments as $i => $arg) {
45+
$argName = $arg->name;
46+
if ($argName === null) {
47+
if ($i !== 0) {
48+
continue;
49+
}
50+
51+
$deprecatedDescriptionType = $this->initializerExprTypeResolver->getType($arg->value, $initializerExprContext);
52+
break;
53+
}
54+
55+
if ($argName->toString() !== 'message') {
56+
continue;
57+
}
58+
59+
$deprecatedDescriptionType = $this->initializerExprTypeResolver->getType($arg->value, $initializerExprContext);
60+
break;
61+
}
62+
}
63+
}
64+
65+
if ($deprecatedDescriptionType !== null) {
66+
$constantStrings = $deprecatedDescriptionType->getConstantStrings();
67+
if (count($constantStrings) === 1) {
68+
$deprecatedDescription = $constantStrings[0]->getValue();
69+
}
70+
}
71+
72+
return [$isDeprecated, $deprecatedDescription];
73+
}
74+
75+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\Generator\NodeHandler;
4+
5+
use Generator;
6+
use PhpParser\Node;
7+
use PhpParser\Node\Param;
8+
use PhpParser\Node\Stmt;
9+
use PHPStan\Analyser\ExpressionContext;
10+
use PHPStan\Analyser\Generator\ExprAnalysisRequest;
11+
use PHPStan\Analyser\Generator\GeneratorScope;
12+
use PHPStan\Analyser\Generator\NodeCallbackRequest;
13+
use PHPStan\Analyser\Scope;
14+
use PHPStan\DependencyInjection\AutowiredService;
15+
16+
#[AutowiredService]
17+
final class ParamHandler
18+
{
19+
20+
public function __construct(
21+
private AttrGroupsHandler $attrGroupsHandler,
22+
)
23+
{
24+
}
25+
26+
/**
27+
* @param (callable(Node, Scope, callable(Node, Scope): void): void)|null $alternativeNodeCallback
28+
*/
29+
public function processParam(
30+
Stmt $stmt,
31+
Param $param,
32+
GeneratorScope $scope,
33+
?callable $alternativeNodeCallback,
34+
): Generator
35+
{
36+
yield from $this->attrGroupsHandler->processAttributeGroups($stmt, $param->attrGroups, $scope, $alternativeNodeCallback);
37+
yield new NodeCallbackRequest($param, $scope);
38+
if ($param->type !== null) {
39+
yield new NodeCallbackRequest($param->type, $scope);
40+
}
41+
if ($param->default === null) {
42+
return;
43+
}
44+
45+
yield new ExprAnalysisRequest($stmt, $param->default, $scope, ExpressionContext::createDeep(), $alternativeNodeCallback);
46+
}
47+
48+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\Generator\NodeHandler;
4+
5+
use Generator;
6+
use PhpParser\Node;
7+
use PhpParser\Node\ComplexType;
8+
use PhpParser\Node\Expr;
9+
use PhpParser\Node\Identifier;
10+
use PhpParser\Node\Name;
11+
use PhpParser\Node\Stmt\Return_;
12+
use PhpParser\NodeTraverser;
13+
use PHPStan\Analyser\Generator\GeneratorScope;
14+
use PHPStan\Analyser\Generator\NodeCallbackRequest;
15+
use PHPStan\Analyser\Generator\StmtsAnalysisRequest;
16+
use PHPStan\Analyser\ImpurePoint;
17+
use PHPStan\Analyser\Scope;
18+
use PHPStan\Analyser\StatementContext;
19+
use PHPStan\DependencyInjection\AutowiredService;
20+
use PHPStan\Node\ExecutionEndNode;
21+
use PHPStan\Node\InPropertyHookNode;
22+
use PHPStan\Node\PropertyAssignNode;
23+
use PHPStan\Node\PropertyHookReturnStatementsNode;
24+
use PHPStan\Node\ReturnStatement;
25+
use PHPStan\Parser\LineAttributesVisitor;
26+
use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection;
27+
use PHPStan\ShouldNotHappenException;
28+
use PHPStan\Type\Type;
29+
use function array_merge;
30+
31+
#[AutowiredService]
32+
final class PropertyHooksHandler
33+
{
34+
35+
public function __construct(
36+
private AttrGroupsHandler $attrGroupsHandler,
37+
private StatementPhpDocsHelper $phpDocsHelper,
38+
private ParamHandler $paramHandler,
39+
private DeprecatedAttributeHelper $deprecatedAttributeHelper,
40+
)
41+
{
42+
}
43+
44+
/**
45+
* @param Node\PropertyHook[] $hooks
46+
* @param (callable(Node, Scope, callable(Node, Scope): void): void)|null $alternativeNodeCallback
47+
*/
48+
public function processPropertyHooks(
49+
Node\Stmt $stmt,
50+
Identifier|Name|ComplexType|null $nativeTypeNode,
51+
?Type $phpDocType,
52+
string $propertyName,
53+
array $hooks,
54+
GeneratorScope $scope,
55+
?callable $alternativeNodeCallback,
56+
): Generator
57+
{
58+
if (!$scope->isInClass()) {
59+
throw new ShouldNotHappenException();
60+
}
61+
62+
$classReflection = $scope->getClassReflection();
63+
64+
foreach ($hooks as $hook) {
65+
yield new NodeCallbackRequest($hook, $scope);
66+
yield from $this->attrGroupsHandler->processAttributeGroups($stmt, $hook->attrGroups, $scope, $alternativeNodeCallback);
67+
68+
[, $phpDocParameterTypes,,,, $phpDocThrowType,,,,,,,, $phpDocComment] = $this->phpDocsHelper->getPhpDocs($scope, $hook);
69+
70+
foreach ($hook->params as $param) {
71+
yield from $this->paramHandler->processParam($stmt, $param, $scope, $alternativeNodeCallback);
72+
}
73+
74+
[$isDeprecated, $deprecatedDescription] = $this->deprecatedAttributeHelper->getDeprecatedAttribute($scope, $hook);
75+
76+
$hookScope = $scope->enterPropertyHook(
77+
$hook,
78+
$propertyName,
79+
$nativeTypeNode,
80+
$phpDocType,
81+
$phpDocParameterTypes,
82+
$phpDocThrowType,
83+
$deprecatedDescription,
84+
$isDeprecated,
85+
$phpDocComment,
86+
);
87+
$hookReflection = $hookScope->getFunction();
88+
if (!$hookReflection instanceof PhpMethodFromParserNodeReflection) {
89+
throw new ShouldNotHappenException();
90+
}
91+
92+
if (!$classReflection->hasNativeProperty($propertyName)) {
93+
throw new ShouldNotHappenException();
94+
}
95+
96+
$propertyReflection = $classReflection->getNativeProperty($propertyName);
97+
98+
yield new NodeCallbackRequest(new InPropertyHookNode(
99+
$classReflection,
100+
$hookReflection,
101+
$propertyReflection,
102+
$hook,
103+
), $hookScope);
104+
105+
$stmts = $hook->getStmts();
106+
if ($stmts === null) {
107+
return;
108+
}
109+
110+
if ($hook->body instanceof Expr) {
111+
// enrich attributes of nodes in short hook body statements
112+
$traverser = new NodeTraverser(
113+
new LineAttributesVisitor($hook->body->getStartLine(), $hook->body->getEndLine()),
114+
);
115+
$traverser->traverse($stmts);
116+
}
117+
118+
$gatheredReturnStatements = [];
119+
$executionEnds = [];
120+
$methodImpurePoints = [];
121+
$statementResult = (yield new StmtsAnalysisRequest($stmts, $hookScope, StatementContext::createTopLevel(), static function (Node $node, Scope $scope, $nodeCallback) use ($hookScope, &$gatheredReturnStatements, &$executionEnds, &$hookImpurePoints): void {
122+
$nodeCallback($node, $scope);
123+
if ($scope->getFunction() !== $hookScope->getFunction()) {
124+
return;
125+
}
126+
if ($scope->isInAnonymousFunction()) {
127+
return;
128+
}
129+
if ($node instanceof PropertyAssignNode) {
130+
$hookImpurePoints[] = new ImpurePoint(
131+
$scope,
132+
$node,
133+
'propertyAssign',
134+
'property assignment',
135+
true,
136+
);
137+
return;
138+
}
139+
if ($node instanceof ExecutionEndNode) {
140+
$executionEnds[] = $node;
141+
return;
142+
}
143+
if (!$node instanceof Return_) {
144+
return;
145+
}
146+
147+
$gatheredReturnStatements[] = new ReturnStatement($scope, $node);
148+
}))->toPublic();
149+
150+
yield new NodeCallbackRequest(new PropertyHookReturnStatementsNode(
151+
$hook,
152+
$gatheredReturnStatements,
153+
$statementResult,
154+
$executionEnds,
155+
array_merge($statementResult->getImpurePoints(), $methodImpurePoints),
156+
$classReflection,
157+
$hookReflection,
158+
$propertyReflection,
159+
), $hookScope);
160+
}
161+
}
162+
163+
}

0 commit comments

Comments
 (0)