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
5 changes: 4 additions & 1 deletion src/Facades/Quantum.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Aether\Facades;

use Aether\Circuit\BatchBuilder;
use Aether\Circuit\CircuitBuilder;
use Aether\Contracts\QuantumDevice;
use Aether\Entropy\EntropyGenerator;
Expand All @@ -17,8 +18,10 @@
/**
* Facade for the QuantumManager.
*
* @method static QuantumDevice driver(?string $name = null)
* @method static string getDefaultDriver()
* @method static QuantumDevice driver(string|\UnitEnum|null $driver = null)
* @method static CircuitBuilder circuit(?string $driver = null)
* @method static BatchBuilder batch(array<array-key, CircuitBuilder> $circuits, ?string $driver = null)
* @method static EntropyGenerator entropy(?string $driver = null)
* @method static \Aether\Bridge\PythonBridge bridge()
* @method static void extend(string $name, Closure $callback)
Expand Down
74 changes: 74 additions & 0 deletions tests/Unit/Facades/QuantumFacadeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

use Aether\Facades\Quantum;
use Aether\QuantumManager;

/**
* The facade forwards every call to QuantumManager at runtime, but IDEs and
* static analysis only know the methods listed as @method annotations, so the
* docblock must mirror the manager: every public method, with its return type.
*/

/** @return array<string, string|null> method name => annotated return type */
function facadeMethodAnnotations(): array
{
$docblock = (string) (new ReflectionClass(Quantum::class))->getDocComment();

preg_match_all('/@method\s+static\s+(?:(.+?)\s+)?([A-Za-z_]\w*)\(/', $docblock, $matches, PREG_SET_ORDER);

$annotations = [];

foreach ($matches as $match) {
$annotations[$match[2]] = $match[1] !== '' ? $match[1] : null;
}

return $annotations;
}

/** @return array<string, ReflectionMethod> method name => reflection, every public method callable through the facade */
function managerPublicMethods(): array
{
$methods = [];

foreach ((new ReflectionClass(QuantumManager::class))->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
if (! $method->isStatic() && ! $method->isConstructor() && ! str_starts_with($method->getName(), '__')) {
$methods[$method->getName()] = $method;
}
}

return $methods;
}

it('annotates every public method QuantumManager declares itself', function () {
$declared = array_filter(
managerPublicMethods(),
fn (ReflectionMethod $method): bool => $method->getDeclaringClass()->getName() === QuantumManager::class,
);

expect(array_values(array_diff(array_keys($declared), array_keys(facadeMethodAnnotations()))))->toBe([]);
});

it('does not annotate methods the manager no longer has', function () {
$stale = array_diff(array_keys(facadeMethodAnnotations()), array_keys(managerPublicMethods()));

expect(array_values($stale))->toBe([]);
});

it('annotates each method with the return type the manager declares', function () {
$methods = managerPublicMethods();

foreach (facadeMethodAnnotations() as $name => $annotated) {
$declared = $methods[$name]->getReturnType();

if (! $declared instanceof ReflectionNamedType || $annotated === null) {
continue;
}

$expected = $declared->isBuiltin() ? $declared->getName() : basename(str_replace('\\', '/', $declared->getName()));
$actual = basename(str_replace('\\', '/', $annotated));

expect($actual)->toBe($expected, "@method {$name}() is annotated as returning {$annotated}, the manager returns {$declared->getName()}");
}
});