1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Node;
4:
5: use Override;
6: use PhpParser\Node;
7: use PhpParser\Node\Stmt\For_;
8: use PhpParser\Node\Stmt\Foreach_;
9: use PhpParser\NodeAbstract;
10: use PHPStan\Node\Variable\VariableWrite;
11: use PHPStan\Turbo\ReferencedByTurboExtension;
12: use PHPStan\Type\Type;
13:
14: /**
15: * All local-variable write sites of a function-like body, with the set of
16: * those whose written value was read on some path afterwards, and the set of
17: * variable names the body mentions at all.
18: *
19: * Emitted right after the body's ReturnStatementsNode, with the scope inside
20: * the function-like. Arrow functions have no node of their own - their writes
21: * belong to the enclosing function-like.
22: *
23: * @api
24: */
25: #[ReferencedByTurboExtension(key: 'variableWritesNode')]
26: final class VariableWritesNode extends NodeAbstract implements VirtualNode
27: {
28:
29: /**
30: * @param list<VariableWrite> $writes
31: * @param array<int, true> $readWriteIds
32: * @param array<int, true> $usedWriteIds
33: * @param array<int, true> $coveredWriteIds
34: * @param array<int, true> $overwrittenWriteIds
35: * @param array<string, true> $readVariableNames
36: * @param array<int, Type> $redundantWriteTypes
37: * @param array<string, true> $referencedVariableNames
38: * @param array<string, true> $untrackedVariableNames
39: * @param array<int, Foreach_|For_> $variableOverwritingLoops
40: */
41: public function __construct(
42: private Node\FunctionLike $functionLike,
43: private array $writes,
44: private array $readWriteIds,
45: private array $usedWriteIds,
46: private array $coveredWriteIds,
47: private array $overwrittenWriteIds,
48: private array $readVariableNames,
49: private array $redundantWriteTypes,
50: private array $referencedVariableNames,
51: private array $untrackedVariableNames,
52: private array $variableOverwritingLoops,
53: private bool $opaque,
54: private bool $allVariableNamesReferenced,
55: )
56: {
57: parent::__construct($functionLike->getAttributes());
58: }
59:
60: public function getFunctionLike(): Node\FunctionLike
61: {
62: return $this->functionLike;
63: }
64:
65: /**
66: * @return list<VariableWrite>
67: */
68: public function getWrites(): array
69: {
70: return $this->writes;
71: }
72:
73: /**
74: * The write whose target is this exact node (a parameter's or closure
75: * use's variable), if it is tracked.
76: */
77: public function getWriteForNode(Node\Expr\Variable $variable): ?VariableWrite
78: {
79: foreach ($this->writes as $write) {
80: if ($write->getNode() === $variable) {
81: return $write;
82: }
83: }
84:
85: return null;
86: }
87:
88: /**
89: * Whether a construct that can observe every variable by name without
90: * reading its current value (func_get_args()) appears in the body.
91: */
92: public function areAllVariableNamesReferenced(): bool
93: {
94: return $this->allVariableNamesReferenced;
95: }
96:
97: /** Whether the value reaches an observable use, directly or through another write. */
98: public function isUsed(VariableWrite $write): bool
99: {
100: return isset($this->usedWriteIds[$write->getId()]);
101: }
102:
103: /**
104: * Whether the value flows into a write that is never read at all - that
105: * write is the one to report, this one only feeds it.
106: */
107: public function flowsIntoNeverReadWrite(VariableWrite $write): bool
108: {
109: return isset($this->coveredWriteIds[$write->getId()]);
110: }
111:
112: /**
113: * Whether some path from the write reaches another write replacing the
114: * variable (or the written offset) - for a write that is never read, the
115: * value is replaced before anything looks at it.
116: */
117: public function isOverwritten(VariableWrite $write): bool
118: {
119: return isset($this->overwrittenWriteIds[$write->getId()]);
120: }
121:
122: /** Whether some path from the write reaches a read of the written value. */
123: public function isRead(VariableWrite $write): bool
124: {
125: return isset($this->readWriteIds[$write->getId()]);
126: }
127:
128: /**
129: * Whether the variable name appears at a read site anywhere in the body,
130: * regardless of which writes the read observed.
131: */
132: public function isVariableEverRead(string $variableName): bool
133: {
134: return isset($this->readVariableNames[$variableName]);
135: }
136:
137: /**
138: * The type of the assigned value when the write assigns the value the
139: * variable provably already has, null otherwise.
140: */
141: public function getRedundantType(VariableWrite $write): ?Type
142: {
143: return $this->redundantWriteTypes[$write->getId()] ?? null;
144: }
145:
146: /**
147: * The loop statement that binds this write in its head - a foreach key
148: * or value variable, a for-loop initial assignment - when the variable
149: * was assigned before the loop and is read after it with no assignment
150: * in between other than the loop's own bindings and updates: the loop
151: * takes over a variable still in use, rather than a spent loop variable.
152: * Null for every other write.
153: *
154: * @return Foreach_|For_|null
155: */
156: public function getVariableOverwritingLoop(VariableWrite $write): ?Node\Stmt
157: {
158: return $this->variableOverwritingLoops[$write->getId()] ?? null;
159: }
160:
161: /**
162: * Whether the body mentions the variable at all: a read, a write, a
163: * statement naming it (global, static, a reference alias), or a construct
164: * that can observe every variable (eval, include, a dynamic compact() or
165: * $$name, func_get_args()).
166: */
167: public function isVariableReferenced(string $variableName): bool
168: {
169: return $this->allVariableNamesReferenced
170: || $this->opaque
171: || isset($this->referencedVariableNames[$variableName]);
172: }
173:
174: /**
175: * Variables whose writes escape the body (by-ref parameters and uses,
176: * global/static variables, reference aliases) - every write counts as used.
177: */
178: public function isUntracked(string $variableName): bool
179: {
180: return isset($this->untrackedVariableNames[$variableName]);
181: }
182:
183: /**
184: * The body contains a construct (goto) that defeats reaching-write tracking.
185: */
186: public function isOpaque(): bool
187: {
188: return $this->opaque;
189: }
190:
191: #[Override]
192: public function getType(): string
193: {
194: return 'PHPStan_Node_VariableWritesNode';
195: }
196:
197: /**
198: * @return string[]
199: */
200: #[Override]
201: public function getSubNodeNames(): array
202: {
203: return [];
204: }
205:
206: }
207: