1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Node\Printer;
4:
5: use Override;
6: use PhpParser\Node;
7: use PhpParser\Node\Expr;
8: use PhpParser\Node\Scalar\String_;
9: use PhpParser\PrettyPrinter\Standard;
10: use PHPStan\DependencyInjection\AutowiredService;
11: use PHPStan\Node\BooleanAndNode;
12: use PHPStan\Node\BooleanOrNode;
13: use PHPStan\Node\Expr\AlwaysRememberedExpr;
14: use PHPStan\Node\Expr\CloneReinitializationExpr;
15: use PHPStan\Node\Expr\ExistingArrayDimFetch;
16: use PHPStan\Node\Expr\ForeachValueByRefExpr;
17: use PHPStan\Node\Expr\IntertwinedVariableByReferenceWithExpr;
18: use PHPStan\Node\Expr\NativeTypeExpr;
19: use PHPStan\Node\Expr\OriginalForeachKeyExpr;
20: use PHPStan\Node\Expr\OriginalForeachValueExpr;
21: use PHPStan\Node\Expr\ParameterVariableOriginalValueExpr;
22: use PHPStan\Node\Expr\PossiblyImpureCallExpr;
23: use PHPStan\Node\Expr\PropertyInitializationExpr;
24: use PHPStan\Node\Expr\SetExistingOffsetValueTypeExpr;
25: use PHPStan\Node\Expr\SetOffsetValueTypeExpr;
26: use PHPStan\Node\Expr\TypeExpr;
27: use PHPStan\Node\Expr\UnsetOffsetExpr;
28: use PHPStan\Node\FunctionCallableNode;
29: use PHPStan\Node\InstantiationCallableNode;
30: use PHPStan\Node\IssetExpr;
31: use PHPStan\Node\MethodCallableNode;
32: use PHPStan\Node\StaticMethodCallableNode;
33: use PHPStan\Type\VerbosityLevel;
34: use function preg_match;
35: use function sprintf;
36: use function str_contains;
37:
38: /**
39: * @api
40: */
41: #[AutowiredService(as: Printer::class)]
42: final class Printer extends Standard
43: {
44:
45: /**
46: * Every construct the printer descends into comes back through here, and
47: * each level of a chain is printed again in turn for its own expression
48: * key, so a chain of depth N costs O(N^2). Remembering each expression's
49: * printed form on its node makes the levels below it O(1) — and it is the
50: * very same cache ExprPrinter fills, so a key printed once is never
51: * rebuilt, wherever it was first reached from.
52: *
53: * Only the standalone form is remembered: at a lower precedence the
54: * printer may parenthesise, and a form containing a newline was indented
55: * for the level it was printed at, while expression keys are printed at
56: * the top level.
57: */
58: #[Override]
59: protected function p(
60: Node $node,
61: int $precedence = self::MAX_PRECEDENCE,
62: int $lhsPrecedence = self::MAX_PRECEDENCE,
63: bool $parentFormatPreserved = false,
64: ): string
65: {
66: if (
67: !$node instanceof Expr
68: || $precedence !== self::MAX_PRECEDENCE
69: || $lhsPrecedence !== self::MAX_PRECEDENCE
70: ) {
71: return parent::p($node, $precedence, $lhsPrecedence, $parentFormatPreserved);
72: }
73:
74: // ExprPrinter::printExpr() caches a multi-line form too, printed at the top level - nested
75: // here, it would need the current indentation
76: $printed = $node->getAttribute(ExprPrinter::ATTRIBUTE_CACHE_KEY);
77: if ($printed !== null && !str_contains($printed, "\n")) {
78: return $printed;
79: }
80:
81: $printed = parent::p($node, parentFormatPreserved: $parentFormatPreserved);
82: if (!str_contains($printed, "\n")) {
83: $node->setAttribute(ExprPrinter::ATTRIBUTE_CACHE_KEY, $printed);
84: }
85:
86: return $printed;
87: }
88:
89: /**
90: * Normalize curly-brace member access with a constant string name to the
91: * bareword form, so that e.g. `$obj->{'n'}` and `$obj->n` (or `$obj->{'n'}()`
92: * and `$obj->n()`) produce identical expression keys and are treated as the
93: * same member by the analyser.
94: */
95: #[Override]
96: protected function pObjectProperty(Node $node): string
97: {
98: if (
99: $node instanceof String_
100: && preg_match('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/', $node->value) === 1
101: ) {
102: return $node->value;
103: }
104:
105: return parent::pObjectProperty($node);
106: }
107:
108: protected function pPHPStan_Node_TypeExpr(TypeExpr $expr): string // phpcs:ignore
109: {
110: return sprintf('__phpstanType(%s)', $expr->getExprType()->describe(VerbosityLevel::precise()));
111: }
112:
113: protected function pPHPStan_Node_NativeTypeExpr(NativeTypeExpr $expr): string // phpcs:ignore
114: {
115: return sprintf('__phpstanNativeType(%s, %s)', $expr->getPhpDocType()->describe(VerbosityLevel::precise()), $expr->getNativeType()->describe(VerbosityLevel::precise()));
116: }
117:
118: protected function pPHPStan_Node_UnsetOffsetExpr(UnsetOffsetExpr $expr): string // phpcs:ignore
119: {
120: return sprintf('__phpstanUnsetOffset(%s, %s)', $this->p($expr->getVar()), $this->p($expr->getDim()));
121: }
122:
123: protected function pPHPStan_Node_ExistingArrayDimFetch(ExistingArrayDimFetch $expr): string // phpcs:ignore
124: {
125: return sprintf('__phpstanExistingArrayDimFetch(%s, %s)', $this->p($expr->getVar()), $this->p($expr->getDim()));
126: }
127:
128: protected function pPHPStan_Node_SetOffsetValueTypeExpr(SetOffsetValueTypeExpr $expr): string // phpcs:ignore
129: {
130: return sprintf('__phpstanSetOffsetValueType(%s, %s, %s)', $this->p($expr->getVar()), $expr->getDim() !== null ? $this->p($expr->getDim()) : 'null', $this->p($expr->getValue()));
131: }
132:
133: protected function pPHPStan_Node_SetExistingOffsetValueTypeExpr(SetExistingOffsetValueTypeExpr $expr): string // phpcs:ignore
134: {
135: return sprintf('__phpstanSetExistingOffsetValueType(%s, %s, %s)', $this->p($expr->getVar()), $this->p($expr->getDim()), $this->p($expr->getValue()));
136: }
137:
138: protected function pPHPStan_Node_AlwaysRememberedExpr(AlwaysRememberedExpr $expr): string // phpcs:ignore
139: {
140: return sprintf('__phpstanRemembered(%s)', $this->p($expr->getExpr()));
141: }
142:
143: protected function pPHPStan_Node_PossiblyImpureCallExpr(PossiblyImpureCallExpr $expr): string // phpcs:ignore
144: {
145: return sprintf('__phpstanPossiblyImpure(%s, %s)', $this->p($expr->callExpr), $this->p($expr->impactedExpr));
146: }
147:
148: protected function pPHPStan_Node_PropertyInitializationExpr(PropertyInitializationExpr $expr): string // phpcs:ignore
149: {
150: return sprintf('__phpstanPropertyInitialization(%s)', $expr->getPropertyName());
151: }
152:
153: protected function pPHPStan_Node_CloneReinitializationExpr(CloneReinitializationExpr $expr): string // phpcs:ignore
154: {
155: return sprintf('__phpstanCloneReinitialization(%s)', $expr->getPropertyName());
156: }
157:
158: protected function pPHPStan_Node_ForeachValueByRefExpr(ForeachValueByRefExpr $expr): string // phpcs:ignore
159: {
160: return sprintf('__phpstanForeachValueByRef(%s)', $this->p($expr->getExpr()));
161: }
162:
163: protected function pPHPStan_Node_ParameterVariableOriginalValueExpr(ParameterVariableOriginalValueExpr $expr): string // phpcs:ignore
164: {
165: return sprintf('__phpstanParameterVariableOriginalValue(%s)', $expr->getVariableName());
166: }
167:
168: protected function pPHPStan_Node_OriginalForeachKeyExpr(OriginalForeachKeyExpr $expr): string // phpcs:ignore
169: {
170: return sprintf('__phpstanOriginalForeachKey(%s)', $expr->getVariableName());
171: }
172:
173: protected function pPHPStan_Node_OriginalForeachValueExpr(OriginalForeachValueExpr $expr): string // phpcs:ignore
174: {
175: return sprintf('__phpstanOriginalForeachValue(%s)', $expr->getVariableName());
176: }
177:
178: protected function pPHPStan_Node_IntertwinedVariableByReferenceWithExpr(IntertwinedVariableByReferenceWithExpr $expr): string // phpcs:ignore
179: {
180: return sprintf('__phpstanIntertwinedVariableByReference(%s, %s, %s)', $expr->getVariableName(), $this->p($expr->getExpr()), $this->p($expr->getAssignedExpr()));
181: }
182:
183: protected function pPHPStan_Node_IssetExpr(IssetExpr $expr): string // phpcs:ignore
184: {
185: return sprintf('__phpstanIssetExpr(%s)', $this->p($expr->getExpr()));
186: }
187:
188: protected function pPHPStan_Node_BooleanOrNode(BooleanOrNode $expr): string // phpcs:ignore
189: {
190: return sprintf('__phpstanBooleanOr(%s, %s)', $this->p($expr->getOriginalNode()->left), $this->p($expr->getOriginalNode()->right));
191: }
192:
193: protected function pPHPStan_Node_BooleanAndNode(BooleanAndNode $expr): string // phpcs:ignore
194: {
195: return sprintf('__phpstanBooleanAnd(%s, %s)', $this->p($expr->getOriginalNode()->left), $this->p($expr->getOriginalNode()->right));
196: }
197:
198: protected function pPHPStan_Node_FunctionCallableNode(FunctionCallableNode $expr): string // phpcs:ignore
199: {
200: return sprintf('__phpstanFunctionCallable(%s)', $this->p($expr->getOriginalNode()));
201: }
202:
203: protected function pPHPStan_Node_MethodCallableNode(MethodCallableNode $expr): string // phpcs:ignore
204: {
205: return sprintf('__phpstanMethodCallable(%s)', $this->p($expr->getOriginalNode()));
206: }
207:
208: protected function pPHPStan_Node_StaticMethodCallableNode(StaticMethodCallableNode $expr): string // phpcs:ignore
209: {
210: return sprintf('__phpstanStaticMethodCallable(%s)', $this->p($expr->getOriginalNode()));
211: }
212:
213: protected function pPHPStan_Node_InstantiationCallableNode(InstantiationCallableNode $expr): string // phpcs:ignore
214: {
215: return sprintf('__phpstanInstantiationCallable(%s)', $this->p($expr->getOriginalNode()));
216: }
217:
218: }
219: