Skip to content

Commit

Permalink
fix: handle class tokens only when needed during lexing
Browse files Browse the repository at this point in the history
Major refactor of how the lexer handles class/interface/enums, which
aims to avoid potentially expensive operations (like `class_exists`
which can call the autoloader).

This results in a minor performance bump, and also fixes some issues
concerning shaped array keys matching class names.
  • Loading branch information
romm committed Apr 2, 2024
1 parent c8174bf commit c4be758
Show file tree
Hide file tree
Showing 27 changed files with 498 additions and 558 deletions.
9 changes: 2 additions & 7 deletions src/Type/Parser/Factory/LexingTypeParserFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use CuyZ\Valinor\Type\Parser\CachedParser;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeParserSpecification;
use CuyZ\Valinor\Type\Parser\Lexer\NativeLexer;
use CuyZ\Valinor\Type\Parser\Lexer\ObjectLexer;
use CuyZ\Valinor\Type\Parser\Lexer\SpecificationsLexer;
use CuyZ\Valinor\Type\Parser\LexingParser;
use CuyZ\Valinor\Type\Parser\TypeParser;

Expand All @@ -27,12 +27,7 @@ public function get(TypeParserSpecification ...$specifications): TypeParser

private function buildTypeParser(TypeParserSpecification ...$specifications): TypeParser
{
$lexer = new ObjectLexer();

foreach ($specifications as $specification) {
$lexer = $specification->manipulateLexer($lexer);
}

$lexer = new SpecificationsLexer($specifications);
$lexer = new NativeLexer($lexer);

$parser = new LexingParser($lexer);
Expand Down
91 changes: 86 additions & 5 deletions src/Type/Parser/Factory/Specifications/AliasSpecification.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
namespace CuyZ\Valinor\Type\Parser\Factory\Specifications;

use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
use CuyZ\Valinor\Type\Parser\Lexer\AliasLexer;
use CuyZ\Valinor\Type\Parser\Lexer\TypeLexer;
use CuyZ\Valinor\Type\Parser\Lexer\Token\ObjectToken;
use CuyZ\Valinor\Type\Parser\Lexer\Token\TraversingToken;
use CuyZ\Valinor\Type\Parser\TypeParser;
use CuyZ\Valinor\Utility\Reflection\PhpParser;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionClass;
use ReflectionFunction;
use Reflector;
Expand All @@ -17,16 +19,95 @@ final class AliasSpecification implements TypeParserSpecification
{
public function __construct(
/** @var ReflectionClass<object>|ReflectionFunction */
private Reflector $reflection
private Reflector $reflection,
) {}

public function manipulateLexer(TypeLexer $lexer): TypeLexer
public function manipulateToken(TraversingToken $token): TraversingToken
{
return new AliasLexer($lexer, $this->reflection);
$symbol = $token->symbol();

// Matches the case where a class extends a class with the same name but
// in a different namespace.
if ($symbol === $this->reflection->getShortName() && Reflection::classOrInterfaceExists($symbol)) {
return $token;
}

$alias = $this->resolveAlias($symbol);

if (strtolower($alias) !== strtolower($symbol)) {
/** @var class-string $alias */
return new ObjectToken($alias);
}

$namespaced = $this->resolveNamespaced($symbol);

if ($namespaced !== $symbol) {
/** @var class-string $namespaced */
return new ObjectToken($namespaced);
}

return $token;
}

public function manipulateParser(TypeParser $parser, TypeParserFactory $typeParserFactory): TypeParser
{
return $parser;
}

private function resolveAlias(string $symbol): string
{
$alias = $symbol;

$namespaceParts = explode('\\', $symbol);
$lastPart = array_shift($namespaceParts);

if ($lastPart) {
$alias = strtolower($lastPart);
}

$aliases = PhpParser::parseUseStatements($this->reflection);

if (! isset($aliases[$alias])) {
return $symbol;
}

if ($aliases[$alias] === $symbol) {
return $symbol;
}

$full = $aliases[$alias];

if (! empty($namespaceParts)) {
$full .= '\\' . implode('\\', $namespaceParts);
}

return $full;
}

private function resolveNamespaced(string $symbol): string
{
$reflection = $this->reflection;

if ($reflection instanceof ReflectionFunction) {
$classReflection = $reflection->getClosureScopeClass();

if ($classReflection && $classReflection->getFileName() === $reflection->getFileName()) {
$reflection = $classReflection;
}
}

$namespace = $reflection->getNamespaceName();

if (! $namespace) {
return $symbol;
}

$full = $namespace . '\\' . $symbol;

if (Reflection::classOrInterfaceExists($full)) {
return $full;
}

return $symbol;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,25 @@
namespace CuyZ\Valinor\Type\Parser\Factory\Specifications;

use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
use CuyZ\Valinor\Type\Parser\Lexer\ClassContextLexer;
use CuyZ\Valinor\Type\Parser\Lexer\TypeLexer;
use CuyZ\Valinor\Type\Parser\Lexer\Token\ObjectToken;
use CuyZ\Valinor\Type\Parser\Lexer\Token\TraversingToken;
use CuyZ\Valinor\Type\Parser\TypeParser;

/** @internal */
final class ClassContextSpecification implements TypeParserSpecification
{
public function __construct(
/** @var class-string */
private string $className
private string $className,
) {}

public function manipulateLexer(TypeLexer $lexer): TypeLexer
public function manipulateToken(TraversingToken $token): TraversingToken
{
return new ClassContextLexer($lexer, $this->className);
if ($token->symbol() === 'self' || $token->symbol() === 'static') {
return new ObjectToken($this->className);
}

return $token;
}

public function manipulateParser(TypeParser $parser, TypeParserFactory $typeParserFactory): TypeParser
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@

use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
use CuyZ\Valinor\Type\Parser\GenericCheckerParser;
use CuyZ\Valinor\Type\Parser\Lexer\TypeLexer;
use CuyZ\Valinor\Type\Parser\Lexer\Token\TraversingToken;
use CuyZ\Valinor\Type\Parser\TypeParser;

/** @internal */
final class GenericCheckerSpecification implements TypeParserSpecification
{
public function manipulateLexer(TypeLexer $lexer): TypeLexer
public function manipulateToken(TraversingToken $token): TraversingToken
{
return $lexer;
return $token;
}

public function manipulateParser(TypeParser $parser, TypeParserFactory $typeParserFactory): TypeParser
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
namespace CuyZ\Valinor\Type\Parser\Factory\Specifications;

use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
use CuyZ\Valinor\Type\Parser\Lexer\TypeAliasLexer;
use CuyZ\Valinor\Type\Parser\Lexer\TypeLexer;
use CuyZ\Valinor\Type\Parser\Lexer\Token\TraversingToken;
use CuyZ\Valinor\Type\Parser\Lexer\Token\TypeToken;
use CuyZ\Valinor\Type\Parser\TypeParser;
use CuyZ\Valinor\Type\Type;

Expand All @@ -15,12 +15,18 @@ final class TypeAliasAssignerSpecification implements TypeParserSpecification
{
public function __construct(
/** @var array<string, Type> */
private array $aliases
private array $aliases,
) {}

public function manipulateLexer(TypeLexer $lexer): TypeLexer
public function manipulateToken(TraversingToken $token): TraversingToken
{
return new TypeAliasLexer($lexer, $this->aliases);
$symbol = $token->symbol();

if (isset($this->aliases[$symbol])) {
return new TypeToken($this->aliases[$symbol], $symbol);
}

return $token;
}

public function manipulateParser(TypeParser $parser, TypeParserFactory $typeParserFactory): TypeParser
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
namespace CuyZ\Valinor\Type\Parser\Factory\Specifications;

use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
use CuyZ\Valinor\Type\Parser\Lexer\TypeLexer;
use CuyZ\Valinor\Type\Parser\Lexer\Token\TraversingToken;
use CuyZ\Valinor\Type\Parser\TypeParser;

/** @internal */
interface TypeParserSpecification
{
public function manipulateLexer(TypeLexer $lexer): TypeLexer;
public function manipulateToken(TraversingToken $token): TraversingToken;

public function manipulateParser(TypeParser $parser, TypeParserFactory $typeParserFactory): TypeParser;
}
111 changes: 0 additions & 111 deletions src/Type/Parser/Lexer/AliasLexer.php

This file was deleted.

43 changes: 0 additions & 43 deletions src/Type/Parser/Lexer/ClassContextLexer.php

This file was deleted.

Loading

0 comments on commit c4be758

Please sign in to comment.