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
18 changes: 9 additions & 9 deletions test/Transpiler/Monomorphize/ArraySugarIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ public function testCollectionWithArraySugarSpecializesToArray(): void
$this->compile();

$fqn = Registry::generatedFqn(
'App\\Containers\\Collection',
[new TypeRef('App\\Models\\User')],
'App\\ArraySugar\\Containers\\Collection',
[new TypeRef('App\\ArraySugar\\Models\\User')],
);
$file = $this->fqnToPath($fqn);
self::assertFileExists($file);
Expand All @@ -51,12 +51,12 @@ public function testCollectionWithArraySugarSpecializesToArray(): void
self::assertStringContainsString('private array $items', $content, 'T[] property must lower to `array`');
self::assertStringContainsString('public function all(): array', $content, 'T[] return type must lower to `array`');
self::assertStringContainsString(
'public function first(): ?\\App\\Models\\User',
'public function first(): ?\\App\\ArraySugar\\Models\\User',
$content,
'?T return type must specialize to ?<concrete>',
);
self::assertStringContainsString(
'public function __construct(\\App\\Models\\User ...$items)',
'public function __construct(\\App\\ArraySugar\\Models\\User ...$items)',
$content,
'variadic T must specialize to <concrete>',
);
Expand Down Expand Up @@ -85,8 +85,8 @@ public function testRuntimeNullableReturnEnforcesConcreteType(): void
$this->compile();

$fqn = Registry::generatedFqn(
'App\\Containers\\Collection',
[new TypeRef('App\\Models\\User')],
'App\\ArraySugar\\Containers\\Collection',
[new TypeRef('App\\ArraySugar\\Models\\User')],
);
$collectionFile = $this->fqnToPath($fqn);

Expand All @@ -98,10 +98,10 @@ public function testRuntimeNullableReturnEnforcesConcreteType(): void
require '{$this->targetDir}/Containers/Collection.php';
require '{$collectionFile}';

\$c = new \\{$fqn}(new \\App\\Models\\User('alice'), new \\App\\Models\\User('bob'));
\$c = new \\{$fqn}(new \\App\\ArraySugar\\Models\\User('alice'), new \\App\\ArraySugar\\Models\\User('bob'));

\$first = \$c->first();
echo \$first instanceof \\App\\Models\\User ? "FIRST_OK" : "FIRST_BAD";
echo \$first instanceof \\App\\ArraySugar\\Models\\User ? "FIRST_OK" : "FIRST_BAD";
echo "\\n";

\$all = \$c->all();
Expand All @@ -127,7 +127,7 @@ public function testRuntimeNullableReturnEnforcesConcreteType(): void
self::assertSame('FIRST_OK', $output[0]);
self::assertSame('ALL_OK', $output[1]);
self::assertSame('EMPTY_NULL_OK', $output[2]);
self::assertSame('App\\Models\\User', $output[3]);
self::assertSame('App\\ArraySugar\\Models\\User', $output[3]);
self::assertSame('NULLABLE_OK', $output[4]);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ public function testBoundIsSatisfiedByImplementingClass(): void

$result = $compiler->compile($sources, $sourceDir, $this->targetDir, $this->cacheDir);

$boxFqn = Registry::generatedFqn('App\\Containers\\Box', [new TypeRef('App\\Models\\Tag')]);
$boxFqn = Registry::generatedFqn('App\\BoundsHappy\\Containers\\Box', [new TypeRef('App\\BoundsHappy\\Models\\Tag')]);
$boxFile = $this->fqnToPath($boxFqn);
self::assertFileExists($boxFile, 'Box<Tag> must specialize when Tag implements \\Stringable (bound satisfied via hierarchy)');

$content = file_get_contents($boxFile);
self::assertStringContainsString('public \\App\\Models\\Tag $item', $content);
self::assertStringContainsString('public \\App\\BoundsHappy\\Models\\Tag $item', $content);

self::assertGreaterThan(0, $result->generatedCount);
}
Expand Down
32 changes: 16 additions & 16 deletions test/Transpiler/Monomorphize/CompilerIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ public function testCompilesGenericFixtureEndToEnd(): void
self::assertSame(5, $result->sourceCount, 'expected 5 source .xphp files (4 top-level + 1 in sub/)');
self::assertSame(2, $result->generatedCount, 'expected 2 specializations (Box<Plastic>, Box<Metal>)');

$boxPlasticFqn = Registry::generatedFqn('App\\Containers\\Box', [new TypeRef('App\\Models\\Plastic')]);
$boxMetalFqn = Registry::generatedFqn('App\\Containers\\Box', [new TypeRef('App\\Models\\Metal')]);
$boxPlasticFqn = Registry::generatedFqn('App\\BoxGeneric\\Containers\\Box', [new TypeRef('App\\BoxGeneric\\Models\\Plastic')]);
$boxMetalFqn = Registry::generatedFqn('App\\BoxGeneric\\Containers\\Box', [new TypeRef('App\\BoxGeneric\\Models\\Metal')]);

$boxPlasticFile = $this->fqnToPath($boxPlasticFqn);
$boxMetalFile = $this->fqnToPath($boxMetalFqn);
Expand All @@ -58,11 +58,11 @@ public function testCompilesGenericFixtureEndToEnd(): void

$boxPlasticContent = file_get_contents($boxPlasticFile);
self::assertStringContainsString('declare (strict_types=1)', $boxPlasticContent, 'specialized class must opt in to strict types');
self::assertStringContainsString('namespace XPHP\\Generated\\App\\Containers\\Box', $boxPlasticContent);
self::assertStringContainsString('namespace XPHP\\Generated\\App\\BoxGeneric\\Containers\\Box', $boxPlasticContent);
self::assertStringContainsString('class ' . self::shortName($boxPlasticFqn), $boxPlasticContent);
self::assertStringContainsString('public \\App\\Models\\Plastic $item', $boxPlasticContent);
self::assertStringContainsString('public function set(\\App\\Models\\Plastic $val)', $boxPlasticContent);
self::assertStringContainsString('public function get(): \\App\\Models\\Plastic', $boxPlasticContent);
self::assertStringContainsString('public \\App\\BoxGeneric\\Models\\Plastic $item', $boxPlasticContent);
self::assertStringContainsString('public function set(\\App\\BoxGeneric\\Models\\Plastic $val)', $boxPlasticContent);
self::assertStringContainsString('public function get(): \\App\\BoxGeneric\\Models\\Plastic', $boxPlasticContent);

$useFile = $this->targetDir . '/Use.php';
self::assertFileExists($useFile);
Expand Down Expand Up @@ -121,20 +121,20 @@ public function testGeneratedCodeIsLoadableViaPsr4Autoloader(): void
// without explicit require statements
// -> reflection on the specialized class reports the real concrete type
$loader = new \Composer\Autoload\ClassLoader();
$loader->addPsr4('App\\', $this->targetDir);
$loader->addPsr4('App\\BoxGeneric\\', $this->targetDir);
$loader->addPsr4(Registry::GENERATED_NAMESPACE_PREFIX . '\\', $this->cacheDir . '/Generated');
$loader->register();

try {
self::assertTrue(class_exists('App\\Models\\Plastic'), 'user class App\\Models\\Plastic must autoload from the PSR-4 target dir');
self::assertTrue(class_exists('App\\BoxGeneric\\Models\\Plastic'), 'user class App\\BoxGeneric\\Models\\Plastic must autoload from the PSR-4 target dir');

$boxPlasticFqn = Registry::generatedFqn('App\\Containers\\Box', [new TypeRef('App\\Models\\Plastic')]);
$boxPlasticFqn = Registry::generatedFqn('App\\BoxGeneric\\Containers\\Box', [new TypeRef('App\\BoxGeneric\\Models\\Plastic')]);
self::assertTrue(class_exists($boxPlasticFqn), "specialized class {$boxPlasticFqn} must autoload from the PSR-4 cache dir");

// Confirm the autoloaded specialized class carries the real concrete type on its property.
$type = (new \ReflectionProperty($boxPlasticFqn, 'item'))->getType();
self::assertInstanceOf(\ReflectionNamedType::class, $type);
self::assertSame('App\\Models\\Plastic', $type->getName());
self::assertSame('App\\BoxGeneric\\Models\\Plastic', $type->getName());
} finally {
$loader->unregister();
}
Expand All @@ -149,13 +149,13 @@ public function testInstanceofAgainstOriginalTemplateMatchesAllSpecializations()
$compiler->compile($sources, $this->sourceDir, $this->targetDir, $this->cacheDir);

$loader = new \Composer\Autoload\ClassLoader();
$loader->addPsr4('App\\', $this->targetDir);
$loader->addPsr4('App\\BoxGeneric\\', $this->targetDir);
$loader->addPsr4(Registry::GENERATED_NAMESPACE_PREFIX . '\\', $this->cacheDir . '/Generated');
$loader->register();

try {
$boxPlasticFqn = Registry::generatedFqn('App\\Containers\\Box', [new TypeRef('App\\Models\\Plastic')]);
$boxMetalFqn = Registry::generatedFqn('App\\Containers\\Box', [new TypeRef('App\\Models\\Metal')]);
$boxPlasticFqn = Registry::generatedFqn('App\\BoxGeneric\\Containers\\Box', [new TypeRef('App\\BoxGeneric\\Models\\Plastic')]);
$boxMetalFqn = Registry::generatedFqn('App\\BoxGeneric\\Containers\\Box', [new TypeRef('App\\BoxGeneric\\Models\\Metal')]);

// Use reflection rather than `new $fqn()` to avoid coupling this test to whether
// the box_generic Box<T> template happens to declare a constructor at any given
Expand All @@ -166,11 +166,11 @@ public function testInstanceofAgainstOriginalTemplateMatchesAllSpecializations()

// Both specializations satisfy `instanceof OriginalTemplate` via the
// marker interface emitted at the original FQN.
self::assertTrue($plasticRefl->implementsInterface('App\\Containers\\Box'));
self::assertTrue($metalRefl->implementsInterface('App\\Containers\\Box'));
self::assertTrue($plasticRefl->implementsInterface('App\\BoxGeneric\\Containers\\Box'));
self::assertTrue($metalRefl->implementsInterface('App\\BoxGeneric\\Containers\\Box'));

// And reflection sees the marker as an interface, not the old class.
$r = new \ReflectionClass('App\\Containers\\Box');
$r = new \ReflectionClass('App\\BoxGeneric\\Containers\\Box');
self::assertTrue($r->isInterface(), 'original generic class FQN must now be the marker interface');
} finally {
$loader->unregister();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public function testFunctionCallSitesAreRewrittenToMangledFqnNames(): void
self::assertFileExists($usePath);
$content = file_get_contents($usePath);

self::assertSame(2, preg_match_all('/\\\\App\\\\identity_T_[0-9a-f]+\(/', $content));
self::assertSame(2, preg_match_all('/\\\\App\\\\GenericFunction\\\\identity_T_[0-9a-f]+\(/', $content));
self::assertStringNotContainsString('identity<', $content);
}

Expand All @@ -85,7 +85,7 @@ public function testRuntimeExecutionOfSpecializedFunctions(): void
foreach (\$m[1] as \$i => \$mangled) {
\$type = \$m[2][\$i];
\$sample = \$type === 'int' ? 42 : 'hi';
\$fqn = '\\\\App\\\\' . \$mangled;
\$fqn = '\\\\App\\\\GenericFunction\\\\' . \$mangled;
\$out = \$fqn(\$sample);
\$expected = \$type === 'int' ? 'integer' : 'string';
echo gettype(\$out) === \$expected ? "OK_{\$type}" : "BAD_{\$type}", "\\n";
Expand Down
43 changes: 19 additions & 24 deletions test/Transpiler/Monomorphize/GenericInterfaceIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ public function testGenericInterfaceSpecializesAndIsImplementedBySpecializedClas
$this->compile();

$ifaceFqn = Registry::generatedFqn(
'App\\Containers\\Container',
[new TypeRef('App\\Models\\Plastic')],
'App\\GenericInterface\\Containers\\Container',
[new TypeRef('App\\GenericInterface\\Models\\Plastic')],
);
$boxFqn = Registry::generatedFqn(
'App\\Containers\\Box',
[new TypeRef('App\\Models\\Plastic')],
'App\\GenericInterface\\Containers\\Box',
[new TypeRef('App\\GenericInterface\\Models\\Plastic')],
);

$ifaceFile = $this->fqnToPath($ifaceFqn);
Expand All @@ -56,7 +56,7 @@ public function testGenericInterfaceSpecializesAndIsImplementedBySpecializedClas

$ifaceContent = file_get_contents($ifaceFile);
self::assertStringContainsString('interface ' . self::shortName($ifaceFqn), $ifaceContent, 'specialized declaration must remain an interface');
self::assertStringContainsString('public function get(): \\App\\Models\\Plastic', $ifaceContent, 'T return type must be substituted in the interface signature');
self::assertStringContainsString('public function get(): \\App\\GenericInterface\\Models\\Plastic', $ifaceContent, 'T return type must be substituted in the interface signature');

$boxContent = file_get_contents($boxFile);
self::assertStringContainsString('class ' . self::shortName($boxFqn), $boxContent);
Expand All @@ -80,29 +80,24 @@ public function testGenericInterfaceTemplateIsReplacedByEmptyMarkerInOutput(): v

public function testSpecializedClassIsInstanceOfOriginalInterfaceMarker(): void
{
// F3 from the review: `$x instanceof App\Containers\Container` must hold for the
// specialized Box, traveling the chain
// Box_<Polymer> implements Container_<Polymer> extends App\Containers\Container.
// F3 from the review: `$x instanceof App\GenericInterface\Containers\Container`
// must hold for the specialized Box, traveling the chain
// Box_<Polymer> implements Container_<Polymer> extends App\GenericInterface\Containers\Container.
// This is the marker-interface contract (item 5) applied to the interface case.
//
// Polymer is fixture-unique (see Models/Polymer.xphp) to avoid PHP's process-wide
// class-table cache returning a Box<Plastic> body loaded by an earlier integration
// test (multiple fixtures specialize Box<Plastic> at the same hash but only this
// one's body implements Container<T>).
$this->compile();

$loader = new \Composer\Autoload\ClassLoader();
$loader->addPsr4('App\\', $this->targetDir);
$loader->addPsr4('App\\GenericInterface\\', $this->targetDir);
$loader->addPsr4(Registry::GENERATED_NAMESPACE_PREFIX . '\\', $this->cacheDir . '/Generated');
$loader->register();

try {
$boxFqn = Registry::generatedFqn('App\\Containers\\Box', [new TypeRef('App\\Models\\Polymer')]);
$box = new $boxFqn(new \App\Models\Polymer('PET'));
$boxFqn = Registry::generatedFqn('App\\GenericInterface\\Containers\\Box', [new TypeRef('App\\GenericInterface\\Models\\Polymer')]);
$box = new $boxFqn(new \App\GenericInterface\Models\Polymer('PET'));

self::assertInstanceOf('App\\Containers\\Container', $box, 'specialized Box must transitively satisfy the original Container interface marker');
self::assertInstanceOf('App\\GenericInterface\\Containers\\Container', $box, 'specialized Box must transitively satisfy the original Container interface marker');

$r = new \ReflectionClass('App\\Containers\\Container');
$r = new \ReflectionClass('App\\GenericInterface\\Containers\\Container');
self::assertTrue($r->isInterface(), 'original generic interface FQN must now be the marker interface');
} finally {
$loader->unregister();
Expand All @@ -114,12 +109,12 @@ public function testSpecializedClassIsInstanceOfSpecializedInterfaceAtRuntime():
$this->compile();

$ifaceFqn = Registry::generatedFqn(
'App\\Containers\\Container',
[new TypeRef('App\\Models\\Plastic')],
'App\\GenericInterface\\Containers\\Container',
[new TypeRef('App\\GenericInterface\\Models\\Plastic')],
);
$boxFqn = Registry::generatedFqn(
'App\\Containers\\Box',
[new TypeRef('App\\Models\\Plastic')],
'App\\GenericInterface\\Containers\\Box',
[new TypeRef('App\\GenericInterface\\Models\\Plastic')],
);

$ifaceFile = $this->fqnToPath($ifaceFqn);
Expand All @@ -135,7 +130,7 @@ public function testSpecializedClassIsInstanceOfSpecializedInterfaceAtRuntime():
require '{$ifaceFile}';
require '{$boxFile}';

\$box = new \\{$boxFqn}(new \\App\\Models\\Plastic('red'));
\$box = new \\{$boxFqn}(new \\App\\GenericInterface\\Models\\Plastic('red'));
echo \$box instanceof \\{$ifaceFqn} ? "INSTANCEOF_OK" : "INSTANCEOF_BAD";
echo "\\n";
echo \$box->get()->color === 'red' ? "GET_OK" : "GET_BAD";
Expand All @@ -153,7 +148,7 @@ public function testSpecializedClassIsInstanceOfSpecializedInterfaceAtRuntime():
self::assertSame(0, $exit, "runtime check failed:\n" . implode("\n", $output));
self::assertSame('INSTANCEOF_OK', $output[0]);
self::assertSame('GET_OK', $output[1]);
self::assertSame('App\\Models\\Plastic', $output[2]);
self::assertSame('App\\GenericInterface\\Models\\Plastic', $output[2]);
}

public function testAllOutputFilesAreSyntacticallyValid(): void
Expand Down
6 changes: 3 additions & 3 deletions test/Transpiler/Monomorphize/GenericMethodIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public function testCallSitesAreRewrittenToMangledNames(): void
$content = file_get_contents($usePath);

// Three call sites (2 int + 1 string), all rewritten to fully-qualified mangled refs.
self::assertSame(3, preg_match_all('/\\\\App\\\\Util::identity_T_[0-9a-f]+\\(/', $content));
self::assertSame(3, preg_match_all('/\\\\App\\\\GenericMethod\\\\Util::identity_T_[0-9a-f]+\\(/', $content));
// The two int call sites must share the same mangled name (single specialization
// per unique arg list, not per call site — locks the alreadyGenerated dedupe).
\preg_match_all('/identity_T_([0-9a-f]+)/', $content, $matches);
Expand All @@ -91,7 +91,7 @@ public function testRuntimeExecutionPreservesGenericMethodSemantics(): void
declare(strict_types=1);
require '{$utilPath}';

\$asInt = \\App\\Util::identity_T_FILLED_AT_RUNTIME(42);
\$asInt = \\App\\GenericMethod\\Util::identity_T_FILLED_AT_RUNTIME(42);
PHP);

// Pull the mangled FQNs out of Util.php and call them directly. We assert each
Expand All @@ -104,7 +104,7 @@ public function testRuntimeExecutionPreservesGenericMethodSemantics(): void
$type = $matches[2][$i];
$sample = $type === 'int' ? '42' : "'hello'";
$expected = $gettypeName[$type];
$script .= "\$out{$i} = \\App\\Util::{$mangled}({$sample});\n";
$script .= "\$out{$i} = \\App\\GenericMethod\\Util::{$mangled}({$sample});\n";
$script .= "echo gettype(\$out{$i}) === '{$expected}' ? 'OK_{$type}' : 'BAD_{$type}', \"\\n\";\n";
}
file_put_contents($runScript, $script);
Expand Down
Loading
Loading