From 48adf800d022aac9dd7a5d66e30df3b7ca0f1f24 Mon Sep 17 00:00:00 2001 From: corgab Date: Tue, 8 Sep 2026 17:26:07 +0000 Subject: [PATCH 1/2] fix: annotate batch() and getDefaultDriver() on the Quantum facade Quantum::batch() worked at runtime through Facade::__callStatic() but was missing from the @method annotations, so IDEs and static analysis treated it as undefined. The docblock now lists every public method QuantumManager declares, and a test compares the annotations with the manager's public methods so the two cannot drift apart again. Closes #43 --- src/Facades/Quantum.php | 3 ++ tests/Unit/Facades/QuantumFacadeTest.php | 45 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 tests/Unit/Facades/QuantumFacadeTest.php diff --git a/src/Facades/Quantum.php b/src/Facades/Quantum.php index 7451739..9bdfe33 100644 --- a/src/Facades/Quantum.php +++ b/src/Facades/Quantum.php @@ -4,6 +4,7 @@ namespace Aether\Facades; +use Aether\Circuit\BatchBuilder; use Aether\Circuit\CircuitBuilder; use Aether\Contracts\QuantumDevice; use Aether\Entropy\EntropyGenerator; @@ -17,8 +18,10 @@ /** * Facade for the QuantumManager. * + * @method static string getDefaultDriver() * @method static QuantumDevice driver(?string $name = null) * @method static CircuitBuilder circuit(?string $driver = null) + * @method static BatchBuilder batch(array $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) diff --git a/tests/Unit/Facades/QuantumFacadeTest.php b/tests/Unit/Facades/QuantumFacadeTest.php new file mode 100644 index 0000000..695179b --- /dev/null +++ b/tests/Unit/Facades/QuantumFacadeTest.php @@ -0,0 +1,45 @@ +getDocComment(); + + preg_match_all('/@method\s+static\s+\S+\s+([A-Za-z_]\w*)\(/', $docblock, $matches); + + return $matches[1]; +} + +function managerPublicMethods(): array +{ + $methods = []; + + foreach ((new ReflectionClass(QuantumManager::class))->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + if ($method->getDeclaringClass()->getName() === QuantumManager::class && ! $method->isStatic() && ! $method->isConstructor()) { + $methods[] = $method->getName(); + } + } + + return $methods; +} + +it('annotates every public QuantumManager method on the facade', function () { + $missing = array_diff(managerPublicMethods(), facadeMethodAnnotations()); + + expect(array_values($missing))->toBe([]); +}); + +it('annotates batch() with the BatchBuilder it returns', function () { + $docblock = (string) (new ReflectionClass(Quantum::class))->getDocComment(); + + expect($docblock)->toContain('@method static BatchBuilder batch('); +}); From ded56fb68c3fff88f93b77fc83fe0afeec72ad55 Mon Sep 17 00:00:00 2001 From: corgab Date: Tue, 8 Sep 2026 17:32:02 +0000 Subject: [PATCH 2/2] fix: match the facade annotations to the manager's real signatures batch() accepts any array of circuits, since the manager reindexes it, and driver() accepts the enum cases Manager::driver() resolves through driverAlias(). The drift test now parses each annotation, checks the two lists in both directions and compares the annotated return types with the declared ones, instead of only looking for method names. --- src/Facades/Quantum.php | 4 +- tests/Unit/Facades/QuantumFacadeTest.php | 51 +++++++++++++++++++----- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/Facades/Quantum.php b/src/Facades/Quantum.php index 9bdfe33..8ab92eb 100644 --- a/src/Facades/Quantum.php +++ b/src/Facades/Quantum.php @@ -19,9 +19,9 @@ * Facade for the QuantumManager. * * @method static string getDefaultDriver() - * @method static QuantumDevice driver(?string $name = null) + * @method static QuantumDevice driver(string|\UnitEnum|null $driver = null) * @method static CircuitBuilder circuit(?string $driver = null) - * @method static BatchBuilder batch(array $circuits, ?string $driver = null) + * @method static BatchBuilder batch(array $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) diff --git a/tests/Unit/Facades/QuantumFacadeTest.php b/tests/Unit/Facades/QuantumFacadeTest.php index 695179b..48cd494 100644 --- a/tests/Unit/Facades/QuantumFacadeTest.php +++ b/tests/Unit/Facades/QuantumFacadeTest.php @@ -8,38 +8,67 @@ /** * 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 name each public method the manager declares. + * docblock must mirror the manager: every public method, with its return type. */ + +/** @return array method name => annotated return type */ function facadeMethodAnnotations(): array { $docblock = (string) (new ReflectionClass(Quantum::class))->getDocComment(); - preg_match_all('/@method\s+static\s+\S+\s+([A-Za-z_]\w*)\(/', $docblock, $matches); + 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 $matches[1]; + return $annotations; } +/** @return array 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->getDeclaringClass()->getName() === QuantumManager::class && ! $method->isStatic() && ! $method->isConstructor()) { - $methods[] = $method->getName(); + if (! $method->isStatic() && ! $method->isConstructor() && ! str_starts_with($method->getName(), '__')) { + $methods[$method->getName()] = $method; } } return $methods; } -it('annotates every public QuantumManager method on the facade', function () { - $missing = array_diff(managerPublicMethods(), facadeMethodAnnotations()); +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($missing))->toBe([]); + expect(array_values(array_diff(array_keys($declared), array_keys(facadeMethodAnnotations()))))->toBe([]); }); -it('annotates batch() with the BatchBuilder it returns', function () { - $docblock = (string) (new ReflectionClass(Quantum::class))->getDocComment(); +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([]); +}); - expect($docblock)->toContain('@method static BatchBuilder batch('); +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()}"); + } });