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.
125 lines
6.7 KiB
125 lines
6.7 KiB
<?php
|
|
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
use TypePhp\CompilerTest;
|
|
use TypePhp\Exception\TestError;
|
|
use TypePhp\Type;
|
|
|
|
final class StdAttributeTypeTest extends BaseTest
|
|
{
|
|
private function translate(string $source): array
|
|
{
|
|
global $translator;
|
|
$directory = sys_get_temp_dir() . '/std-attribute-type-' . bin2hex(random_bytes(6));
|
|
mkdir($directory);
|
|
$file = $directory . '/declarations.php';
|
|
file_put_contents($file, '<?php ' . $source);
|
|
try {
|
|
$translator = CompilerTest::create(TYPEPHP_ROOT_PATH);
|
|
$translator->addFiles([$file]);
|
|
$translator->prepareFile($file);
|
|
return [file_get_contents($translator->convertFile($file)), $translator];
|
|
} finally {
|
|
unlink($file);
|
|
rmdir($directory);
|
|
}
|
|
}
|
|
|
|
public function testMatchingParameterStorageTypes(): void
|
|
{
|
|
[$code, $compiler] = $this->translate(<<<'PHP'
|
|
function vector_arg(#[StdVector(Type::Int)] box $v): void { $v[] = 1; }
|
|
function array_arg(#[StdArray(Type::Int, [2, 3])] box $v): void { $v[1][2] = 1; }
|
|
function array_arg_scalar(#[StdArray(Type::Int, 3)] box $v): void { $v[2] = 1; }
|
|
function array_arg_vector(#[StdArray(Type::Int, [3])] box $v): void { $v[2] = 1; }
|
|
function map_arg(#[StdMap(Type::Str, Type::Int)] box $v): void { $v['a'] = 2; }
|
|
function ordered_arg(#[StdOrderedMap(Type::Int, Type::Str)] box $v): void { $v[0] = 'a'; }
|
|
function list_arg(#[StdList(Type::Int)] array $v): void { $v[] = 3; }
|
|
function dict_arg(#[StdDict(Type::Str, Type::Int)] array &$v): void { $v['a'] = 4; }
|
|
PHP);
|
|
self::assertStringContainsString('php_vector_arg(php::Var v)', $code);
|
|
self::assertStringContainsString('php_array_arg(php::Var v)', $code);
|
|
self::assertStringContainsString('php::StdArray<php::StdArray<php::Int, 3>, 2>', $code);
|
|
self::assertStringContainsString('php_list_arg(php::Array v)', $code);
|
|
self::assertStringContainsString('php_dict_arg(php::Array & v)', $code);
|
|
$getFunction = new ReflectionMethod($compiler, 'getFunction');
|
|
self::assertSame(Type::VAR, $getFunction->invoke($compiler, 'vector_arg')->argInfoList[0]->type);
|
|
self::assertSame(
|
|
$getFunction->invoke($compiler, 'array_arg_scalar')->argInfoList[0]->stdContainer,
|
|
$getFunction->invoke($compiler, 'array_arg_vector')->argInfoList[0]->stdContainer,
|
|
);
|
|
self::assertSame(Type::ARRAY, $getFunction->invoke($compiler, 'list_arg')->argInfoList[0]->type);
|
|
self::assertSame(Type::ARRAY_REF, $getFunction->invoke($compiler, 'dict_arg')->argInfoList[0]->type);
|
|
}
|
|
|
|
public function testPropertyAnnotationsAndSparseWrites(): void
|
|
{
|
|
[$code, $compiler] = $this->translate(<<<'PHP'
|
|
class User { public int $id = 1; }
|
|
class State {
|
|
#[StdList(Type::Int)] public array $values = [];
|
|
#[StdDict(Type::Str, User::class)] public array $users = [];
|
|
#[StdList(Type::Str)] public $inferred = [];
|
|
#[StdDict(Type::Int, Type::Str)] public static array $labels = [];
|
|
#[StdVector(Type::Int)] public box $vector;
|
|
#[StdArray(Type::Int, [2, 3])] public box $matrix;
|
|
#[StdMap(Type::Str, Type::Int)] public box $map;
|
|
#[StdOrderedMap(Type::Int, User::class)] public $ordered;
|
|
}
|
|
#[Native] class NativeState { #[StdList(Type::Int)] public $values = []; }
|
|
function write_values(State $s, NativeState $n, $key): void {
|
|
$s->values[-2] = 10;
|
|
$s->values[100] = 20;
|
|
$s->values[] = 30;
|
|
$s->values[$key] = 40;
|
|
$s->users['123'] = new User();
|
|
$s->inferred[100] = 'sparse';
|
|
State::$labels[-3] = 'negative';
|
|
$n->values[-5] = 7;
|
|
}
|
|
PHP);
|
|
self::assertStringNotContainsString('safeArrayIndex(', $code);
|
|
self::assertStringContainsString('php::toIntExact(', $code);
|
|
$class = (new ReflectionMethod($compiler, 'getClass'))->invoke($compiler, 'State');
|
|
self::assertSame(Type::ARRAY, $class->getProperty('inferred')->type);
|
|
self::assertSame('list', $class->getProperty('values')->typedArray['kind']);
|
|
self::assertSame('dict', $class->getProperty('users')->typedArray['kind']);
|
|
self::assertSame('User', $class->getProperty('users')->typedArray['class']);
|
|
self::assertSame('vector', $class->getProperty('vector')->stdContainer['kind']);
|
|
self::assertSame('array', $class->getProperty('matrix')->stdContainer['kind']);
|
|
self::assertSame([2, 3], $class->getProperty('matrix')->stdContainer['dimensions']);
|
|
self::assertSame('ordered_map', $class->getProperty('ordered')->stdContainer['kind']);
|
|
self::assertSame(Type::BOX, $class->getProperty('ordered')->type);
|
|
}
|
|
|
|
#[DataProvider('conflictingDeclarations')]
|
|
public function testConflictingDeclarations(string $source, string $message): void
|
|
{
|
|
try {
|
|
$this->translate($source);
|
|
self::fail('Conflicting container declaration was accepted');
|
|
} catch (TestError|\TypePhp\Exception\SyntaxError $error) {
|
|
self::assertStringContainsString($message, $error->getMessage());
|
|
}
|
|
}
|
|
|
|
public static function conflictingDeclarations(): iterable
|
|
{
|
|
foreach (['StdArray(Type::Int, 3)', 'StdVector(Type::Int)', 'StdMap(Type::Str, Type::Int)', 'StdOrderedMap(Type::Int, Type::Str)'] as $attribute) {
|
|
foreach (['array', 'mixed', 'any', '?box', 'box|int'] as $type) {
|
|
yield $attribute . ' parameter ' . $type => ["function f(#[{$attribute}] {$type} \$v): void {}", 'unless it is box'];
|
|
yield $attribute . ' property ' . $type => ["class A { #[{$attribute}] public {$type} \$v; }", 'unless it is box'];
|
|
}
|
|
}
|
|
foreach (['StdList(Type::Int)', 'StdDict(Type::Str, Type::Int)'] as $attribute) {
|
|
foreach (['box', 'mixed', 'any', '?array', 'array|int'] as $type) {
|
|
yield $attribute . ' parameter ' . $type => ["function f(#[{$attribute}] {$type} \$v): void {}", 'unless it is array'];
|
|
yield $attribute . ' property ' . $type => ["class A { #[{$attribute}] public {$type} \$v; }", 'unless it is array'];
|
|
}
|
|
}
|
|
yield 'property container conflict' => ['class A { #[StdVector(Type::Int), StdList(Type::Int)] public $v; }', 'cannot be applied to the same declaration'];
|
|
yield 'property wrong list key' => ['class A { #[StdList(Type::Int)] public array $v = []; } function f(A $a): void { $a->v["1"] = 1; }', 'Typed array key must have type'];
|
|
yield 'property wrong list value' => ['class A { #[StdList(Type::Int)] public array $v = []; } function f(A $a): void { $a->v[] = "1"; }', 'Typed array value must have type'];
|
|
yield 'property dict append' => ['class A { #[StdDict(Type::Int, Type::Int)] public array $v = []; } function f(A $a): void { $a->v[] = 1; }', 'StdDict properties do not support append'];
|
|
}
|
|
}
|
|
|