parent
5f75c5b48c
commit
b46f62da30
8 changed files with 730 additions and 3 deletions
@ -0,0 +1,32 @@ |
||||
<?php |
||||
|
||||
namespace EnumExpressionFixture; |
||||
|
||||
const TWO = 1 + 1; |
||||
const THREE = TWO + 1; |
||||
|
||||
class Provider |
||||
{ |
||||
public const BASE = THREE + 1; |
||||
} |
||||
|
||||
enum Number: int |
||||
{ |
||||
case Two = 1 + 1; |
||||
case Three = THREE; |
||||
case Four = Provider::BASE; |
||||
case Five = [5][0]; |
||||
case Six = self::Two->value + 4; |
||||
case Seven = self::Eight->value - 1; |
||||
case Eight = 8; |
||||
} |
||||
|
||||
enum Word: string |
||||
{ |
||||
case Hello = 'hel' . 'lo'; |
||||
case CaseName = Number::Two->name; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
} |
||||
@ -0,0 +1,151 @@ |
||||
<?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 = '', |
||||
): 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}; |
||||
} |
||||
|
||||
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;', |
||||
]; |
||||
} |
||||
|
||||
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); |
||||
} |
||||
} |
||||
@ -0,0 +1,50 @@ |
||||
--TEST-- |
||||
Backed enum case constant expressions are folded before code generation |
||||
--FILE-- |
||||
<?php |
||||
|
||||
const TWO = 1 + 1; |
||||
const THREE = TWO + 1; |
||||
|
||||
class Provider |
||||
{ |
||||
public const BASE = THREE + 1; |
||||
} |
||||
|
||||
enum Number: int |
||||
{ |
||||
case Two = 1 + 1; |
||||
case Three = THREE; |
||||
case Four = Provider::BASE; |
||||
case Five = [5][0]; |
||||
case Six = self::Two->value + 4; |
||||
case Seven = self::Eight->value - 1; |
||||
case Eight = 8; |
||||
} |
||||
|
||||
enum Word: string |
||||
{ |
||||
case Hello = 'hel' . 'lo'; |
||||
case CaseName = Number::Two->name; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
foreach (Number::cases() as $case) { |
||||
var_dump($case->value); |
||||
} |
||||
foreach (Word::cases() as $case) { |
||||
var_dump($case->value); |
||||
} |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
int(2) |
||||
int(3) |
||||
int(4) |
||||
int(5) |
||||
int(6) |
||||
int(7) |
||||
int(8) |
||||
string(5) "hello" |
||||
string(3) "Two" |
||||
Loading…
Reference in new issue