TypePHP 编译器 https://swoole.com/aot/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

167 lines
5.5 KiB

<?php
/**
* This file is part of TypePHP(AOT).
*
* @link https://www.swoole.com/aot/
* @contact service@swoole.com
*/
use TypePhp\CompilerTest;
use TypePhp\Exception\TestError;
/**
* Backed enum case expressions are folded after declaration composition and
* before C++ generation. No source expression may survive into runtime code.
* @internal
* @coversNothing
*/
final class EnumCaseConstantExpressionTest extends PHPUnit\Framework\TestCase
{
public function testExpressionsAreFinalizedBeforeCodeGeneration(): void
{
global $translator;
$compiler = CompilerTest::create(TYPEPHP_ROOT_PATH);
$translator = $compiler;
$file = TYPEPHP_ROOT_PATH . '/phpunit/code/enum-case-constant-expressions.php';
$compiler->addFiles([$file]);
$compiler->prepareFile($file);
$number = $compiler->getClassDef('EnumExpressionFixture\\Number');
self::assertNotNull($number);
self::assertNull($number->enumCases['Two']);
self::assertArrayHasKey('Two', $number->enumCaseExpressions);
$compiler->convertFile($file);
self::assertSame([
'Two' => 2,
'Three' => 3,
'Four' => 4,
'Five' => 5,
'Six' => 6,
'Seven' => 7,
'Eight' => 8,
], $number->enumCases);
self::assertSame([], $number->enumCaseExpressions);
$word = $compiler->getClassDef('EnumExpressionFixture\\Word');
self::assertNotNull($word);
self::assertSame([
'Hello' => 'hello',
'CaseName' => 'Two',
], $word->enumCases);
self::assertSame([], $word->enumCaseExpressions);
$header = file_get_contents($compiler->getArgInfoHeaderFile($file));
self::assertIsString($header);
self::assertStringContainsString('ZVAL_LONG(&enum_case_Six_value, 6);', $header);
self::assertStringContainsString('zend_string_init_interned("hello"', $header);
self::assertStringNotContainsString('php::getEnumCase', $header);
}
/**
* @dataProvider invalidExpressionProvider
*/
public function testInvalidExpressionFailsBeforeCodeGeneration(
string $expression,
string $expected,
string $declarations = '',
string $additionalCases = '',
): void {
$root = sys_get_temp_dir() . '/typephp-enum-expression-' . bin2hex(random_bytes(8));
mkdir($root, 0777, true);
$file = $root . '/program.php';
file_put_contents($file, <<<PHP
<?php
{$declarations}
enum InvalidEnum: int
{
case A = {$expression};
{$additionalCases}
}
function main(): void {}
PHP);
try {
global $translator;
$compiler = CompilerTest::create($root);
$translator = $compiler;
$compiler->addFiles([$file]);
$compiler->prepareFile($file);
try {
$compiler->convertFile($file);
self::fail('Compilation unexpectedly succeeded');
} catch (TestError $error) {
self::assertStringContainsString($expected, $error->getMessage());
}
self::assertFileDoesNotExist($compiler->getCppFile($file));
} finally {
$this->removeTree($root);
}
}
public static function invalidExpressionProvider(): iterable
{
yield 'unknown runtime constant' => [
'RUNTIME_VALUE',
'backing value must be compile-time evaluable: Constant `RUNTIME_VALUE` is not known at compile time',
];
yield 'wrong scalar result type' => [
'1 / 2',
'backing value must be of type int, float given',
];
yield 'self-reference through enum value' => [
'self::A->value + 1',
'Cannot declare self-referencing constant `InvalidEnum::A`',
];
yield 'self-reference through enum name' => [
'self::A->name === "A" ? 1 : 2',
'Cannot declare self-referencing constant `InvalidEnum::A`',
];
yield 'class constant cycle' => [
'Cycle::A',
'Cannot declare self-referencing constant `Cycle::A`',
'class Cycle { public const A = self::B; public const B = self::A; }',
];
yield 'global constant cycle' => [
'FIRST',
'Cannot declare self-referencing constant `FIRST`',
'const FIRST = SECOND; const SECOND = FIRST;',
];
yield 'mutually recursive enum cases' => [
'self::B->value + 1',
'Cannot declare self-referencing constant `InvalidEnum::A`',
'',
'case B = self::A->value + 1;',
];
yield 'unknown enum case' => [
'self::Missing->value',
'Class constant `InvalidEnum::Missing` not found',
];
yield 'evaluation error' => [
'1 / 0',
'backing value must be compile-time evaluable: Division by zero',
];
}
private function removeTree(string $root): void
{
if (!is_dir($root)) {
return;
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST,
);
foreach ($iterator as $entry) {
if ($entry->isDir()) {
rmdir($entry->getPathname());
} else {
unlink($entry->getPathname());
}
}
rmdir($root);
}
}