Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions src/Analyser/ArgumentsNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PHPStan\Analyser;

use PhpParser\Node as PhpParserNode;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\FuncCall;
Expand All @@ -12,6 +13,7 @@
use PhpParser\Node\Scalar\Int_;
use PhpParser\Node\Scalar\String_;
use PHPStan\Node\Expr\TypeExpr;
use PHPStan\Node\Printer\ExprPrinter;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\ShouldNotHappenException;
Expand Down Expand Up @@ -91,7 +93,7 @@ public static function reorderCallUserFuncArguments(
return [$parametersAcceptor, new FuncCall(
$callbackArg->value,
$passThruArgs,
$callUserFuncCall->getAttributes(),
self::attributesWithoutPrintedForm($callUserFuncCall),
), $acceptsNamedArguments];
}

Expand Down Expand Up @@ -161,7 +163,7 @@ public static function reorderCallUserFuncArrayArguments(
$item->value,
$item->byRef,
$item->unpack,
$item->getAttributes(),
self::attributesWithoutPrintedForm($item),
is_string($key) ? new Identifier($key) : null,
);
}
Expand All @@ -187,7 +189,7 @@ public static function reorderCallUserFuncArrayArguments(
return [$parametersAcceptor, new FuncCall(
$callbackArg->value,
$passThruArgs,
$callUserFuncArrayCall->getAttributes(),
self::attributesWithoutPrintedForm($callUserFuncArrayCall),
), $acceptsNamedArguments];
}

Expand All @@ -211,7 +213,7 @@ public static function reorderFuncArguments(
return new FuncCall(
$functionCall->name,
$reorderedArgs,
$functionCall->getAttributes(),
self::attributesWithoutPrintedForm($functionCall),
);
}

Expand All @@ -236,7 +238,7 @@ public static function reorderMethodArguments(
$methodCall->var,
$methodCall->name,
$reorderedArgs,
$methodCall->getAttributes(),
self::attributesWithoutPrintedForm($methodCall),
);
}

Expand All @@ -261,7 +263,7 @@ public static function reorderStaticCallArguments(
$staticCall->class,
$staticCall->name,
$reorderedArgs,
$staticCall->getAttributes(),
self::attributesWithoutPrintedForm($staticCall),
);
}

Expand All @@ -285,7 +287,7 @@ public static function reorderNewArguments(
return new New_(
$new->class,
$reorderedArgs,
$new->getAttributes(),
self::attributesWithoutPrintedForm($new),
);
}

Expand Down Expand Up @@ -439,4 +441,20 @@ public static function reorderArgs(ParametersAcceptor $parametersAcceptor, array
return $reorderedArgs;
}

/**
* The printed form of an expression is derived from its own arguments, so
* it must not travel to a node whose arguments were just reordered — the
* normalized call would report the original's expression key, named
* arguments and all.
*
* @return array<string, mixed>
*/
private static function attributesWithoutPrintedForm(PhpParserNode $node): array
{
$attributes = $node->getAttributes();
unset($attributes[ExprPrinter::ATTRIBUTE_CACHE_KEY]);

return $attributes;
}

}
44 changes: 44 additions & 0 deletions src/Node/Printer/Printer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Override;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar\String_;
use PhpParser\PrettyPrinter\Standard;
use PHPStan\DependencyInjection\AutowiredService;
Expand Down Expand Up @@ -36,6 +37,7 @@
use PHPStan\Type\VerbosityLevel;
use function preg_match;
use function sprintf;
use function str_contains;

/**
* @api
Expand All @@ -44,6 +46,48 @@
final class Printer extends Standard
{

/**
* Every construct the printer descends into comes back through here, and
* each level of a chain is printed again in turn for its own expression
* key, so a chain of depth N costs O(N^2). Remembering each expression's
* printed form on its node makes the levels below it O(1) — and it is the
* very same cache ExprPrinter fills, so a key printed once is never
* rebuilt, wherever it was first reached from.
*
* Only the standalone form is remembered: at a lower precedence the
* printer may parenthesise, and a form containing a newline was indented
* for the level it was printed at, while expression keys are printed at
* the top level.
*/
#[Override]
protected function p(
Node $node,
int $precedence = self::MAX_PRECEDENCE,
int $lhsPrecedence = self::MAX_PRECEDENCE,
bool $parentFormatPreserved = false,
): string
{
if (
!$node instanceof Expr
|| $precedence !== self::MAX_PRECEDENCE
|| $lhsPrecedence !== self::MAX_PRECEDENCE
) {
return parent::p($node, $precedence, $lhsPrecedence, $parentFormatPreserved);
}

$printed = $node->getAttribute(ExprPrinter::ATTRIBUTE_CACHE_KEY);
if ($printed !== null) {
return $printed;
}

$printed = parent::p($node, parentFormatPreserved: $parentFormatPreserved);
if (!str_contains($printed, "\n")) {
$node->setAttribute(ExprPrinter::ATTRIBUTE_CACHE_KEY, $printed);
}

return $printed;
}

/**
* Normalize curly-brace member access with a constant string name to the
* bareword form, so that e.g. `$obj->{'n'}` and `$obj->n` (or `$obj->{'n'}()`
Expand Down
Loading