Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce compiled normalizer cache #500

Draft
wants to merge 19 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
'./tests',
'./qa',
])
->notPath('Integration/Normalizer/ExpectedCache/')
->notPath('Fixtures/FunctionWithGroupedImportStatements.php')
->notPath('Fixtures/FunctionWithSeveralImportStatementsInSameUseStatement.php')
->notPath('Fixtures/TwoClassesInDifferentNamespaces.php');
Expand Down
8 changes: 8 additions & 0 deletions infection.json.dist
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
"src"
]
},
"mutators": {
"@default": true,
"CloneRemoval": {
"ignore": [
"CuyZ\\Valinor\\Compiler\\*"
]
}
},
"tmpDir": "var/cache/infection",
"logs": {
"text": "var/infection/infection.log",
Expand Down
1 change: 1 addition & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ parameters:
- qa
excludePaths:
- tests/StaticAnalysis
- tests/Integration/Normalizer/ExpectedCache/
ignoreErrors:
# \PHPStan\Rules\BooleansInConditions
- '#Only booleans are allowed in .* given#'
Expand Down
2 changes: 2 additions & 0 deletions src/Cache/FileSystemCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use CuyZ\Valinor\Definition\FunctionDefinition;
use CuyZ\Valinor\Definition\Repository\Cache\Compiler\ClassDefinitionCompiler;
use CuyZ\Valinor\Definition\Repository\Cache\Compiler\FunctionDefinitionCompiler;
use CuyZ\Valinor\Normalizer\Transformer\EvaluatedTransformer;
use Error;
use FilesystemIterator;
use Traversable;
Expand Down Expand Up @@ -193,6 +194,7 @@ private function compile(mixed $value): string
$code = match (true) {
$value instanceof ClassDefinition => $this->classDefinitionCompiler->compile($value),
$value instanceof FunctionDefinition => $this->functionDefinitionCompiler->compile($value),
$value instanceof EvaluatedTransformer => $value->code,
default => var_export($value, true),
};

Expand Down
6 changes: 4 additions & 2 deletions src/Cache/KeySanitizerCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace CuyZ\Valinor\Cache;

use CuyZ\Valinor\Library\Settings;
use CuyZ\Valinor\Utility\Package;
use Psr\SimpleCache\CacheInterface;
use Traversable;
Expand All @@ -23,7 +24,8 @@ final class KeySanitizerCache implements WarmupCache

public function __construct(
/** @var CacheInterface<EntryType> */
private CacheInterface $delegate
private CacheInterface $delegate,
private Settings $settings,
) {}

/**
Expand All @@ -42,7 +44,7 @@ private function sanitize(string $key): string

$firstPart = strstr($key, "\0", before_needle: true);

return $firstPart . hash('xxh128', $key . self::$version);
return $firstPart . hash('xxh128', $key . $this->settings->hash() . self::$version);
}

public function warmup(): void
Expand Down
5 changes: 5 additions & 0 deletions src/Cache/RuntimeCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace CuyZ\Valinor\Cache;

use CuyZ\Valinor\Normalizer\Transformer\EvaluatedTransformer;
use Psr\SimpleCache\CacheInterface;

/**
Expand Down Expand Up @@ -31,6 +32,10 @@ public function get($key, $default = null): mixed

public function set($key, $value, $ttl = null): bool
{
if ($value instanceof EvaluatedTransformer) {
return true;
}

$this->entries[$key] = $value;

return true;
Expand Down
60 changes: 60 additions & 0 deletions src/Compiler/Compiler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Compiler;

use function str_repeat;
use function str_replace;

/** @internal */
final class Compiler
{
private string $code = '';

/** @var non-negative-int */
private int $indentation = 0;

public function compile(Node ...$nodes): self
{
$compiler = $this;

while ($current = array_shift($nodes)) {
$compiler = $current->compile($compiler);

if ($nodes !== []) {
$compiler = $compiler->write("\n");
}
}

return $compiler;
}

public function sub(): self
{
return new self();
}

public function write(string $code): self
{
$self = clone $this;
$self->code .= $code;

return $self;
}

public function indent(): self
{
$self = clone $this;
$self->indentation++;

return $self;
}

public function code(): string
{
$indent = str_repeat(' ', $this->indentation);

return $indent . str_replace("\n", "\n" . $indent, $this->code);
}
}
53 changes: 53 additions & 0 deletions src/Compiler/Library/NewAttributeNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Compiler\Library;

use CuyZ\Valinor\Compiler\Compiler;
use CuyZ\Valinor\Compiler\Node;
use CuyZ\Valinor\Definition\AttributeDefinition;

use function array_map;
use function serialize;

/** @internal */
final class NewAttributeNode extends Node
{
public function __construct(private AttributeDefinition $attribute) {}

public function compile(Compiler $compiler): Compiler
{
$argumentNodes = $this->argumentNode($this->attribute->arguments);

return $compiler->compile(
Node::newClass(
$this->attribute->class->name,
...$argumentNodes,
),
);
}

/**
* @param array<mixed> $arguments
* @return array<Node>
*/
private function argumentNode(array $arguments): array
{
return array_map(function (mixed $argument) {
if (is_object($argument)) {
return Node::functionCall(
name: 'unserialize',
arguments: [Node::value(serialize($argument))],
);
}

if (is_array($argument)) {
return Node::array($this->argumentNode($argument));
}

/** @var scalar $argument */
return Node::value($argument);
}, $arguments);
}
}
24 changes: 24 additions & 0 deletions src/Compiler/Library/TypeAcceptNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Compiler\Library;

use CuyZ\Valinor\Compiler\Compiler;
use CuyZ\Valinor\Compiler\Native\CompliantNode;
use CuyZ\Valinor\Compiler\Node;
use CuyZ\Valinor\Type\Type;

/** @internal */
final class TypeAcceptNode extends Node
{
public function __construct(
private CompliantNode $node,
private Type $type,
) {}

public function compile(Compiler $compiler): Compiler
{
return $compiler->compile($this->type->compiledAccept($this->node));
}
}
109 changes: 109 additions & 0 deletions src/Compiler/Native/AnonymousClassNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Compiler\Native;

use CuyZ\Valinor\Compiler\Compiler;
use CuyZ\Valinor\Compiler\Node;

use function array_map;
use function array_merge;
use function implode;

/** @internal */
final class AnonymousClassNode extends Node
{
/** @var array<Node> */
private array $arguments = [];

/** @var array<interface-string> */
private array $interfaces = [];

/** @var array<PropertyDeclarationNode> */
private array $properties = [];

/** @var array<non-empty-string, MethodNode> */
private array $methods = [];

public function withArguments(Node ...$arguments): self
{
$self = clone $this;
$self->arguments = array_merge($self->arguments, $arguments);

return $self;
}

/**
* @param interface-string ...$interfaces
*/
public function implements(string ...$interfaces): self
{
$self = clone $this;
$self->interfaces = array_merge($self->interfaces, $interfaces);

return $self;
}

public function withProperties(PropertyDeclarationNode ...$properties): self
{
$self = clone $this;
$self->properties = array_merge($self->properties, $properties);

return $self;
}

public function withMethods(MethodNode ...$methods): self
{
$self = clone $this;

foreach ($methods as $method) {
$self->methods[$method->name()] = $method;
}

return $self;
}

public function hasMethod(string $name): bool
{
return isset($this->methods[$name]);
}

public function compile(Compiler $compiler): Compiler
{
$arguments = implode(', ', array_map(
fn (Node $argument) => $compiler->sub()->compile($argument)->code(),
$this->arguments,
));

$compiler = $compiler->write("new class ($arguments)");

if ($this->interfaces !== []) {
$compiler = $compiler->write(
' implements ' . implode(', ', array_map(
fn (string $interface) => '\\' . $interface,
$this->interfaces,
)),
);
}

$body = [
...array_map(
fn (PropertyDeclarationNode $property) => $compiler->sub()->indent()->compile($property)->code(),
$this->properties,
),
...array_map(
fn (MethodNode $method) => $compiler->sub()->indent()->compile($method)->code(),
$this->methods,
),
];

$compiler = $compiler->write(' {');

if ($body !== []) {
$compiler = $compiler->write(PHP_EOL . implode(PHP_EOL . PHP_EOL, $body) . PHP_EOL);
}

return $compiler->write('}');
}
}
26 changes: 26 additions & 0 deletions src/Compiler/Native/ArrayKeyAccessNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Compiler\Native;

use CuyZ\Valinor\Compiler\Compiler;
use CuyZ\Valinor\Compiler\Node;

/** @internal */
final class ArrayKeyAccessNode extends Node
{
public function __construct(
private Node $node,
private Node $key,
) {}

public function compile(Compiler $compiler): Compiler
{
$key = $compiler->sub()->compile($this->key)->code();

return $compiler
->compile($this->node)
->write('[' . $key . ']');
}
}
Loading
Loading