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<string, true> $readVariableNames
35: * @param array<int, Type> $redundantWriteTypes
36: * @param array<string, true> $referencedVariableNames
37: * @param array<string, true> $untrackedVariableNames
38: * @param array<int, Foreach_|For_> $variableOverwritingLoops
39: */
40: public function __construct(
41: private Node\FunctionLike $functionLike,
42: private array $writes,
43: private array $readWriteIds,
44: private array $usedWriteIds,
45: private array $coveredWriteIds,
46: private array $readVariableNames,
47: private array $redundantWriteTypes,
48: private array $referencedVariableNames,
49: private array $untrackedVariableNames,
50: private array $variableOverwritingLoops,
51: private bool $opaque,
52: private bool $allVariableNamesReferenced,
53: )
54: {
55: parent::__construct($functionLike->getAttributes());
56: }
57:
58: public function getFunctionLike(): Node\FunctionLike
59: {
60: return $this->functionLike;
61: }
62:
63: /**
64: * @return list<VariableWrite>
65: */
66: public function getWrites(): array
67: {
68: return $this->writes;
69: }
70:
71: /**
72: * The write whose target is this exact node (a parameter's or closure
73: * use's variable), if it is tracked.
74: */
75: public function getWriteForNode(Node\Expr\Variable $variable): ?VariableWrite
76: {
77: foreach ($this->writes as $write) {
78: if ($write->getNode() === $variable) {
79: return $write;
80: }
81: }
82:
83: return null;
84: }
85:
86: /**
87: * Whether a construct that can observe every variable by name without
88: * reading its current value (func_get_args()) appears in the body.
89: */
90: public function areAllVariableNamesReferenced(): bool
91: {
92: return $this->allVariableNamesReferenced;
93: }
94:
95: /** Whether the value reaches an observable use, directly or through another write. */
96: public function isUsed(VariableWrite $write): bool
97: {
98: return isset($this->usedWriteIds[$write->getId()]);
99: }
100:
101: /**
102: * Whether the value flows into a write that is never read at all - that
103: * write is the one to report, this one only feeds it.
104: */
105: public function flowsIntoNeverReadWrite(VariableWrite $write): bool
106: {
107: return isset($this->coveredWriteIds[$write->getId()]);
108: }
109:
110: /** Whether some path from the write reaches a read of the written value. */
111: public function isRead(VariableWrite $write): bool
112: {
113: return isset($this->readWriteIds[$write->getId()]);
114: }
115:
116: /**
117: * Whether the variable name appears at a read site anywhere in the body,
118: * regardless of which writes the read observed.
119: */
120: public function isVariableEverRead(string $variableName): bool
121: {
122: return isset($this->readVariableNames[$variableName]);
123: }
124:
125: /**
126: * The type of the assigned value when the write assigns the value the
127: * variable provably already has, null otherwise.
128: */
129: public function getRedundantType(VariableWrite $write): ?Type
130: {
131: return $this->redundantWriteTypes[$write->getId()] ?? null;
132: }
133:
134: /**
135: * The loop statement that binds this write in its head - a foreach key
136: * or value variable, a for-loop initial assignment - when the variable
137: * was assigned before the loop and is read after it with no assignment
138: * in between other than the loop's own bindings and updates: the loop
139: * takes over a variable still in use, rather than a spent loop variable.
140: * Null for every other write.
141: *
142: * @return Foreach_|For_|null
143: */
144: public function getVariableOverwritingLoop(VariableWrite $write): ?Node\Stmt
145: {
146: return $this->variableOverwritingLoops[$write->getId()] ?? null;
147: }
148:
149: /**
150: * Whether the body mentions the variable at all: a read, a write, a
151: * statement naming it (global, static, a reference alias), or a construct
152: * that can observe every variable (eval, include, a dynamic compact() or
153: * $$name, func_get_args()).
154: */
155: public function isVariableReferenced(string $variableName): bool
156: {
157: return $this->allVariableNamesReferenced
158: || $this->opaque
159: || isset($this->referencedVariableNames[$variableName]);
160: }
161:
162: /**
163: * Variables whose writes escape the body (by-ref parameters and uses,
164: * global/static variables, reference aliases) - every write counts as used.
165: */
166: public function isUntracked(string $variableName): bool
167: {
168: return isset($this->untrackedVariableNames[$variableName]);
169: }
170:
171: /**
172: * The body contains a construct (goto) that defeats reaching-write tracking.
173: */
174: public function isOpaque(): bool
175: {
176: return $this->opaque;
177: }
178:
179: #[Override]
180: public function getType(): string
181: {
182: return 'PHPStan_Node_VariableWritesNode';
183: }
184:
185: /**
186: * @return string[]
187: */
188: #[Override]
189: public function getSubNodeNames(): array
190: {
191: return [];
192: }
193:
194: }
195: