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