Skip to content

Commit

Permalink
Represent names using string rather than array of parts
Browse files Browse the repository at this point in the history
In most circumstances we are interested in the whole string, not
the parts split by namespace separator. As names are common, this
representation measurably improves memory usage and performance.
  • Loading branch information
nikic committed May 21, 2023
1 parent df3a705 commit 2364757
Show file tree
Hide file tree
Showing 86 changed files with 423 additions and 1,198 deletions.
67 changes: 39 additions & 28 deletions lib/PhpParser/Node/Name.php
Expand Up @@ -5,11 +5,8 @@
use PhpParser\NodeAbstract;

class Name extends NodeAbstract {
/**
* @var string[] Parts of the name
* @deprecated Use getParts() instead
*/
public $parts;
/** @var string Name as string */
public $name;

/** @var array<string, bool> */
private static $specialClassNames = [
Expand All @@ -26,11 +23,11 @@ class Name extends NodeAbstract {
*/
final public function __construct($name, array $attributes = []) {
$this->attributes = $attributes;
$this->parts = self::prepareName($name);
$this->name = self::prepareName($name);
}

public function getSubNodeNames(): array {
return ['parts'];
return ['name'];
}

/**
Expand All @@ -39,7 +36,7 @@ public function getSubNodeNames(): array {
* @return string[] Parts of name
*/
public function getParts(): array {
return $this->parts;
return \explode('\\', $this->name);
}

/**
Expand All @@ -48,7 +45,10 @@ public function getParts(): array {
* @return string First part of the name
*/
public function getFirst(): string {
return $this->parts[0];
if (false !== $pos = \strpos($this->name, '\\')) {
return \substr($this->name, 0, $pos);
}
return $this->name;
}

/**
Expand All @@ -57,7 +57,10 @@ public function getFirst(): string {
* @return string Last part of the name
*/
public function getLast(): string {
return $this->parts[count($this->parts) - 1];
if (false !== $pos = \strrpos($this->name, '\\')) {
return \substr($this->name, $pos + 1);
}
return $this->name;
}

/**
Expand All @@ -66,7 +69,7 @@ public function getLast(): string {
* @return bool Whether the name is unqualified
*/
public function isUnqualified(): bool {
return 1 === count($this->parts);
return false === \strpos($this->name, '\\');
}

/**
Expand All @@ -75,7 +78,7 @@ public function isUnqualified(): bool {
* @return bool Whether the name is qualified
*/
public function isQualified(): bool {
return 1 < count($this->parts);
return false !== \strpos($this->name, '\\');
}

/**
Expand Down Expand Up @@ -103,7 +106,7 @@ public function isRelative(): bool {
* @return string String representation
*/
public function toString(): string {
return implode('\\', $this->parts);
return $this->name;
}

/**
Expand All @@ -123,7 +126,7 @@ public function toCodeString(): string {
* @return string Lowercased string representation
*/
public function toLowerString(): string {
return strtolower(implode('\\', $this->parts));
return strtolower($this->name);
}

/**
Expand All @@ -132,8 +135,7 @@ public function toLowerString(): string {
* @return bool Whether identifier is a special class name
*/
public function isSpecialClassName(): bool {
return count($this->parts) === 1
&& isset(self::$specialClassNames[strtolower($this->parts[0])]);
return isset(self::$specialClassNames[strtolower($this->name)]);
}

/**
Expand All @@ -143,7 +145,7 @@ public function isSpecialClassName(): bool {
* @return string String representation
*/
public function __toString(): string {
return implode('\\', $this->parts);
return $this->name;
}

/**
Expand All @@ -163,7 +165,16 @@ public function __toString(): string {
* @return static|null Sliced name
*/
public function slice(int $offset, ?int $length = null) {
$numParts = count($this->parts);
if ($offset === 1 && $length === null) {
// Short-circuit the common case.
if (false !== $pos = \strpos($this->name, '\\')) {
return new static(\substr($this->name, $pos + 1));
}
return null;
}

$parts = \explode('\\', $this->name);
$numParts = \count($parts);

$realOffset = $offset < 0 ? $offset + $numParts : $offset;
if ($realOffset < 0 || $realOffset > $numParts) {
Expand All @@ -184,7 +195,7 @@ public function slice(int $offset, ?int $length = null) {
return null;
}

return new static(array_slice($this->parts, $realOffset, $realLength), $this->attributes);
return new static(array_slice($parts, $realOffset, $realLength), $this->attributes);
}

/**
Expand All @@ -209,42 +220,42 @@ public static function concat($name1, $name2, array $attributes = []) {
return null;
}
if (null === $name1) {
return new static(self::prepareName($name2), $attributes);
return new static($name2, $attributes);
}
if (null === $name2) {
return new static(self::prepareName($name1), $attributes);
return new static($name1, $attributes);
} else {
return new static(
array_merge(self::prepareName($name1), self::prepareName($name2)), $attributes
self::prepareName($name1) . '\\' . self::prepareName($name2), $attributes
);
}
}

/**
* Prepares a (string, array or Name node) name for use in name changing methods by converting
* it to an array.
* it to a string.
*
* @param string|string[]|self $name Name to prepare
*
* @return string[] Prepared name
* @return string Prepared name
*/
private static function prepareName($name): array {
private static function prepareName($name): string {
if (\is_string($name)) {
if ('' === $name) {
throw new \InvalidArgumentException('Name cannot be empty');
}

return explode('\\', $name);
return $name;
}
if (\is_array($name)) {
if (empty($name)) {
throw new \InvalidArgumentException('Name cannot be empty');
}

return $name;
return implode('\\', $name);
}
if ($name instanceof self) {
return $name->parts;
return $name->name;
}

throw new \InvalidArgumentException(
Expand Down
6 changes: 3 additions & 3 deletions lib/PhpParser/PrettyPrinter/Standard.php
Expand Up @@ -80,15 +80,15 @@ protected function pAttributeGroup(Node\AttributeGroup $node): string {
// Names

protected function pName(Name $node): string {
return implode('\\', $node->parts);
return $node->name;
}

protected function pName_FullyQualified(Name\FullyQualified $node): string {
return '\\' . implode('\\', $node->parts);
return '\\' . $node->name;
}

protected function pName_Relative(Name\Relative $node): string {
return 'namespace\\' . implode('\\', $node->parts);
return 'namespace\\' . $node->name;
}

// Magic Constants
Expand Down
6 changes: 3 additions & 3 deletions test/PhpParser/Node/NameTest.php
Expand Up @@ -5,13 +5,13 @@
class NameTest extends \PHPUnit\Framework\TestCase {
public function testConstruct() {
$name = new Name(['foo', 'bar']);
$this->assertSame(['foo', 'bar'], $name->parts);
$this->assertSame('foo\bar', $name->name);

$name = new Name('foo\bar');
$this->assertSame(['foo', 'bar'], $name->parts);
$this->assertSame('foo\bar', $name->name);

$name = new Name($name);
$this->assertSame(['foo', 'bar'], $name->parts);
$this->assertSame('foo\bar', $name->name);
}

public function testGet() {
Expand Down
5 changes: 1 addition & 4 deletions test/PhpParser/NodeDumperTest.php
Expand Up @@ -34,10 +34,7 @@ public function provideTestDump() {
[
new Node\Name(['Hallo', 'World']),
'Name(
parts: array(
0: Hallo
1: World
)
name: Hallo\World
)'
],
[
Expand Down
2 changes: 1 addition & 1 deletion test/PhpParser/NodeVisitor/NameResolverTest.php
Expand Up @@ -460,7 +460,7 @@ public function testClassNameIsCaseInsensitive() {
$stmt = $stmts[0];

$assign = $stmt->stmts[1]->expr;
$this->assertSame(['Bar', 'Baz'], $assign->expr->class->parts);
$this->assertSame('Bar\\Baz', $assign->expr->class->name);
}

public function testSpecialClassNamesAreCaseInsensitive() {
Expand Down
2 changes: 1 addition & 1 deletion test/code/formatPreservation/listInsertion.test
Expand Up @@ -141,7 +141,7 @@ function test() {
namespace
Foo;
-----
$stmts[0]->name->parts[0] = 'Xyz';
$stmts[0]->name->name = 'Xyz';
-----
<?php
namespace
Expand Down
8 changes: 2 additions & 6 deletions test/code/parser/errorHandling/eofError.test
Expand Up @@ -7,9 +7,7 @@ array(
0: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
parts: array(
0: foo
)
name: foo
)
)
)
Expand All @@ -22,9 +20,7 @@ array(
0: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
parts: array(
0: foo
)
name: foo
)
)
)
Expand Down

0 comments on commit 2364757

Please sign in to comment.