test(aot): 添加 AOT 编译器功能测试和文档

- 新增匿名类功能测试文件 tests/aot/anonymous-classes.phpt
- 新增数组展开操作符测试文件 tests/aot/array-spread.phpt
- 新增箭头函数测试文件 tests/aot/arrow-functions.phpt
- 新增属性注解测试文件 tests/aot/attributes.phpt
- 新增构造函数属性提升测试文件 tests/aot/constructor-promotion.phpt
- 新增生成器测试文件 tests/aot/generators.phpt
- 新增 trait 基础功能测试文件 tests/aot/trait-basic.phpt
- 更新测试运行脚本支持 main 函数自动调用
- 新增 AOT 编译器语法支持文档 docs/UNSUPPORTED_SYNTAX.md
- 记录 ZVAL 与原生类型性能对比和使用建议
- 文档化扩展模式和二进制可执行文件模式差异
- 详细说明不支持的语法特性包括生成器、可变变量等
pull/1/head
韩天峰 5 months ago
parent 1441a7e5be
commit 5b0346a4ad
  1. 1037
      docs/UNSUPPORTED_SYNTAX.md
  2. 5
      run-tests.php
  3. 171
      tests/aot/anonymous-classes.phpt
  4. 195
      tests/aot/array-spread.phpt
  5. 201
      tests/aot/arrow-functions.phpt
  6. 120
      tests/aot/attributes.phpt
  7. 140
      tests/aot/constructor-promotion.phpt
  8. 92
      tests/aot/generators.phpt
  9. 97
      tests/aot/trait-basic.phpt

File diff suppressed because it is too large Load Diff

@ -2434,6 +2434,11 @@ TEST $file
$bin_file = compile_php_file($test_file);
$args = substr($args, strlen(' -- '));
$cmd = './' . $bin_file . ' ' . $args . $cmdRedirect;
} else {
$content = file_get_contents($test_file);
if (preg_match('/function main\(\)/', $content)) {
file_put_contents($test_file, $content. '<?php main(); ?>');
}
}
if ($valgrind) {

@ -0,0 +1,171 @@
--TEST--
Anonymous Classes - Runtime class definition
--FILE--
<?php
// Test basic anonymous class
function test_basic_anonymous() {
$basic = new class {
public function greet(): string {
return "Hello from anonymous class";
}
};
var_dump($basic->greet());
}
// Test anonymous class with constructor
class Greeter {
private string $greeting;
public function __construct(string $greeting = "Hello") {
$this->greeting = $greeting;
}
public function getGreeting(): string {
return $this->greeting;
}
}
function test_anonymous_with_constructor() {
$withConstructor = new class("Hi") extends Greeter {
public function getGreeting(): string {
return parent::getGreeting() . " World!";
}
};
var_dump($withConstructor->getGreeting());
}
// Test anonymous class implementing interface
interface LoggerInterface {
public function log(string $message): void;
public function getLogs(): array;
}
function test_anonymous_interface() {
$logger = new class implements LoggerInterface {
private array $logs = [];
public function log(string $message): void {
$this->logs[] = date('Y-m-d H:i:s') . " - " . $message;
}
public function getLogs(): array {
return $this->logs;
}
};
$logger->log("First message");
$logger->log("Second message");
var_dump(count($logger->getLogs()));
}
// Test anonymous class with properties
function test_anonymous_properties() {
$config = new class {
public string $name = "Test";
private int $value = 42;
public function getValue(): int {
return $this->value;
}
public function setValue(int $value): void {
$this->value = $value;
}
};
var_dump($config->name);
var_dump($config->getValue());
$config->setValue(100);
var_dump($config->getValue());
}
// Test nested anonymous classes
function test_nested_anonymous() {
$outer = new class {
private object $inner;
public function __construct() {
$this->inner = new class {
public function getMessage(): string {
return "From inner class";
}
};
}
public function getInnerMessage(): string {
return $this->inner->getMessage();
}
};
var_dump($outer->getInnerMessage());
}
// Test anonymous class in array
function test_anonymous_array() {
$classes = [
new class { public function getType() { return "A"; } },
new class { public function getType() { return "B"; } },
new class { public function getType() { return "C"; } },
];
foreach ($classes as $class) {
echo $class->getType() . "\n";
}
}
// Test static method in anonymous class
function test_anonymous_static() {
$static = new class {
private static int $counter = 0;
public static function increment(): int {
return ++self::$counter;
}
public static function getCounter(): int {
return self::$counter;
}
};
var_dump($static::increment());
var_dump($static::increment());
var_dump($static::getCounter());
}
function main() {
// Test basic anonymous
test_basic_anonymous();
// Test anonymous with constructor
test_anonymous_with_constructor();
// Test anonymous interface
test_anonymous_interface();
// Test anonymous properties
test_anonymous_properties();
// Test nested anonymous
test_nested_anonymous();
// Test anonymous array
test_anonymous_array();
// Test anonymous static
test_anonymous_static();
}
?>
--EXPECT--
string(26) "Hello from anonymous class"
string(9) "Hi World!"
int(2)
string(4) "Test"
int(42)
int(100)
string(16) "From inner class"
A
B
C
int(1)
int(2)
int(2)

@ -0,0 +1,195 @@
--TEST--
Spread Operator in Arrays - Array unpacking with ...
--FILE--
<?php
// Test basic array spreading
function test_basic_spread() {
$part1 = [1, 2, 3];
$part2 = [4, 5, 6];
return [...$part1, ...$part2];
}
// Test spread with additional elements
function test_spread_with_elements() {
$middle = [2, 3, 4];
return [1, ...$middle, 5];
}
// Test multiple spreads
function test_multiple_spreads() {
$a = [1, 2];
$b = [3, 4];
$c = [5, 6];
return [...$a, ...$b, ...$c];
}
// Test spread with keys
function test_spread_with_keys() {
$arr1 = ['a' => 1, 'b' => 2];
$arr2 = ['c' => 3, 'd' => 4];
return [...$arr1, ...$arr2];
}
// Test spread in middle of array
function create_user_record($id, $name, $extra = []) {
return [
'id' => $id,
'name' => $name,
...$extra,
'active' => true,
];
}
// Test nested spreading
function test_nested_spread() {
$inner = [7, 8];
$outer = [1, 2, [...$inner], 9, 10];
return $outer;
}
// Test spread with string keys (overwriting)
function test_string_key_spread() {
$defaults = ['status' => 'active', 'role' => 'user'];
$override = ['role' => 'admin'];
return [...$defaults, ...$override];
}
// Test spread empty arrays
function test_spread_empty() {
$empty = [];
$data = [1, 2, 3];
return [...$empty, ...$data, ...$empty];
}
function main() {
// Test basic spread
var_dump(test_basic_spread());
// Test spread with elements
var_dump(test_spread_with_elements());
// Test multiple spreads
var_dump(test_multiple_spreads());
// Test spread with keys
var_dump(test_spread_with_keys());
// Test spread in function return
var_dump(create_user_record(1, 'John', ['email' => 'john@example.com']));
// Test nested spread (note: this creates a nested array)
var_dump(test_nested_spread());
// Test string key spread (last value wins)
var_dump(test_string_key_spread());
// Test spread empty arrays
var_dump(test_spread_empty());
// Test complex real-world example
$baseConfig = ['debug' => false, 'timeout' => 30];
$envConfig = ['timeout' => 60, 'retries' => 3];
$config = [...$baseConfig, ...$envConfig];
var_dump($config);
}
?>
--EXPECT--
array(6) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
[3]=>
int(4)
[4]=>
int(5)
[5]=>
int(6)
}
array(5) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
[3]=>
int(4)
[4]=>
int(5)
}
array(6) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
[3]=>
int(4)
[4]=>
int(5)
[5]=>
int(6)
}
array(4) {
["a"]=>
int(1)
["b"]=>
int(2)
["c"]=>
int(3)
["d"]=>
int(4)
}
array(4) {
["id"]=>
int(1)
["name"]=>
string(4) "John"
["email"]=>
string(16) "john@example.com"
["active"]=>
bool(true)
}
array(5) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
array(2) {
[0]=>
int(7)
[1]=>
int(8)
}
[3]=>
int(9)
[4]=>
int(10)
}
array(2) {
["status"]=>
string(6) "active"
["role"]=>
string(5) "admin"
}
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
array(3) {
["debug"]=>
bool(false)
["timeout"]=>
int(60)
["retries"]=>
int(3)
}

@ -0,0 +1,201 @@
--TEST--
Arrow Functions - PHP 8.1+ short closure syntax
--FILE--
<?php
// Test basic arrow function
function test_basic_arrow() {
$numbers = [1, 2, 3, 4, 5];
$doubled = array_map(fn($n) => $n * 2, $numbers);
return $doubled;
}
// Test arrow function with multiple parameters
function test_multi_param_arrow() {
$pairs = [[1, 2], [3, 4], [5, 6]];
$sums = array_map(fn($a, $b) => $a + $b, ...$pairs);
return $sums;
}
// Test arrow function capturing variables (by value)
function test_captured_variable($multiplier) {
$numbers = [1, 2, 3];
$multiplied = array_map(fn($n) => $n * $multiplier, $numbers);
return $multiplied;
}
// Test nested arrow functions
function test_nested_arrow() {
$numbers = [1, 2, 3, 4];
$result = array_map(
fn($n) => array_reduce([1, 2], fn($carry, $x) => $carry * $x, $n),
$numbers
);
return $result;
}
// Test arrow function in filter
function test_filter_arrow() {
$numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
$evens = array_filter($numbers, fn($n) => $n % 2 === 0);
return array_values($evens);
}
// Test arrow function in reduce
function test_reduce_arrow() {
$numbers = [1, 2, 3, 4, 5];
$product = array_reduce($numbers, fn($carry, $n) => $carry * $n, 1);
return $product;
}
// Test arrow function returning arrays
function test_array_return_arrow() {
$items = [1, 2, 3];
$transformed = array_map(fn($item) => [$item, $item * 2], $items);
return $transformed;
}
// Test chained arrow function calls
class Calculator {
private array $numbers;
public function __construct(array $numbers) {
$this->numbers = $numbers;
}
public function transform(callable $callback): self {
$this->numbers = array_map($callback, $this->numbers);
return $this;
}
public function filter(callable $callback): self {
$this->numbers = array_values(array_filter($this->numbers, $callback));
return $this;
}
public function getNumbers(): array {
return $this->numbers;
}
}
function main() {
// Test basic arrow
var_dump(test_basic_arrow());
// Test multi-param arrow
var_dump(test_multi_param_arrow());
// Test captured variable
var_dump(test_captured_variable(10));
var_dump(test_captured_variable(100));
// Test nested arrow
var_dump(test_nested_arrow());
// Test filter arrow
var_dump(test_filter_arrow());
// Test reduce arrow
var_dump(test_reduce_arrow());
// Test array return
var_dump(test_array_return_arrow());
// Test chained operations
$calc = new Calculator([1, 2, 3, 4, 5]);
$result = $calc
->transform(fn($n) => $n * 2)
->filter(fn($n) => $n > 5)
->transform(fn($n) => $n + 1)
->getNumbers();
var_dump($result);
}
?>
--EXPECT--
array(5) {
[0]=>
int(2)
[1]=>
int(4)
[2]=>
int(6)
[3]=>
int(8)
[4]=>
int(10)
}
array(2) {
[0]=>
int(4)
[1]=>
int(6)
}
array(3) {
[0]=>
int(10)
[1]=>
int(20)
[2]=>
int(30)
}
array(3) {
[0]=>
int(100)
[1]=>
int(200)
[2]=>
int(300)
}
array(4) {
[0]=>
int(2)
[1]=>
int(4)
[2]=>
int(6)
[3]=>
int(8)
}
array(5) {
[0]=>
int(2)
[1]=>
int(4)
[2]=>
int(6)
[3]=>
int(8)
[4]=>
int(10)
}
int(120)
array(3) {
[0]=>
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
[1]=>
array(2) {
[0]=>
int(2)
[1]=>
int(4)
}
[2]=>
array(2) {
[0]=>
int(3)
[1]=>
int(6)
}
}
array(3) {
[0]=>
int(7)
[1]=>
int(9)
[2]=>
int(11)
}

@ -0,0 +1,120 @@
--TEST--
Attributes (Annotations) - PHP 8+ metadata syntax
--SKIPIF--
<?php
echo "skip Attributes/Annotations not supported in AOT";
?>
--FILE--
<?php
// Define attribute classes
#[Attribute(Attribute::TARGET_CLASS)]
class Route {
public string $path;
public array $methods;
public function __construct(string $path, array $methods = ['GET']) {
$this->path = $path;
$this->methods = $methods;
}
}
#[Attribute(Attribute::TARGET_METHOD)]
class Cache {
public int $ttl;
public function __construct(int $ttl = 3600) {
$this->ttl = $ttl;
}
}
#[Attribute(Attribute::TARGET_PROPERTY)]
class Column {
public string $name;
public string $type;
public function __construct(string $name, string $type = 'string') {
$this->name = $name;
$this->type = $type;
}
}
// Use attributes
#[Route('/api/users', ['GET', 'POST'])]
class UserController {
#[Column('id', 'int')]
private int $id;
#[Column('name', 'string')]
private string $name;
#[Cache(ttl: 1800)]
public function getUsers() {
return "Getting users";
}
#[Cache(ttl: 3600)]
public function getUser($id) {
return "Getting user: " . $id;
}
}
#[Route('/api/posts')]
class PostController {
#[Cache]
public function getPosts() {
return "Getting posts";
}
}
function main() {
// Test class attributes
$userController = new ReflectionClass(UserController::class);
$attributes = $userController->getAttributes();
var_dump(count($attributes));
$routeAttr = $attributes[0]->newInstance();
var_dump($routeAttr->path);
var_dump($routeAttr->methods);
// Test method attributes
$getMethod = $userController->getMethod('getUsers');
$methodAttrs = $getMethod->getAttributes();
var_dump(count($methodAttrs));
$cacheAttr = $methodAttrs[0]->newInstance();
var_dump($cacheAttr->ttl);
// Test property attributes
$idProp = $userController->getProperty('id');
$propAttrs = $idProp->getAttributes();
var_dump(count($propAttrs));
$columnAttr = $propAttrs[0]->newInstance();
var_dump($columnAttr->name);
var_dump($columnAttr->type);
// Test another class
$postController = new ReflectionClass(PostController::class);
$postAttrs = $postController->getAttributes();
var_dump(count($postAttrs));
$postRoute = $postAttrs[0]->newInstance();
var_dump($postRoute->path);
}
?>
--EXPECT--
int(1)
string(11) "/api/users"
array(2) {
[0]=>
string(3) "GET"
[1]=>
string(4) "POST"
}
int(1)
int(1800)
int(1)
string(2) "id"
string(3) "int"
int(1)
string(11) "/api/posts"

@ -0,0 +1,140 @@
--TEST--
Constructor Property Promotion - PHP 8+ concise class syntax
--FILE--
<?php
// Test basic constructor promotion
class Point {
public function __construct(
public float $x = 0.0,
public float $y = 0.0
) {}
public function distance(): float {
return sqrt($this->x * $this->x + $this->y * $this->y);
}
}
// Test with visibility modifiers
class User {
public function __construct(
public string $name,
private string $email,
protected int $age = 18
) {}
public function getEmail(): string {
return $this->email;
}
public function getAge(): int {
return $this->age;
}
}
// Test with nullable types
class Product {
public function __construct(
public string $name,
public float $price,
public ?string $description = null,
public int $quantity = 0
) {}
public function getDescription(): string {
return $this->description ?? 'No description';
}
}
// Test mixed traditional and promoted
class Book {
private static int $count = 0;
public function __construct(
public string $title,
public string $author,
private float $price
) {
self::$count++;
}
public function getPriceWithTax(float $taxRate): float {
return $this->price * (1 + $taxRate);
}
public static function getCount(): int {
return self::$count;
}
}
// Test readonly properties (PHP 8.1+)
class Coordinate {
public function __construct(
public readonly float $latitude,
public readonly float $longitude
) {}
}
function main() {
// Test basic promotion
$point = new Point(3.0, 4.0);
var_dump($point->x);
var_dump($point->y);
var_dump($point->distance());
$origin = new Point();
var_dump($origin->x);
var_dump($origin->y);
// Test with visibility
$user = new User('Alice', 'alice@example.com', 25);
var_dump($user->name);
var_dump($user->getEmail());
var_dump($user->getAge());
// Test nullable
$product1 = new Product('Laptop', 999.99, 'High-performance laptop', 10);
var_dump($product1->name);
var_dump($product1->price);
var_dump($product1->getDescription());
var_dump($product1->quantity);
$product2 = new Product('Mouse', 29.99);
var_dump($product2->name);
var_dump($product2->getDescription());
// Test mixed
$book1 = new Book('PHP Guide', 'John Doe', 49.99);
var_dump($book1->title);
var_dump($book1->author);
var_dump($book1->getPriceWithTax(0.1));
$book2 = new Book('Advanced PHP', 'Jane Smith', 59.99);
var_dump(Book::getCount());
// Test readonly
$coord = new Coordinate(40.7128, -74.0060);
var_dump($coord->latitude);
var_dump($coord->longitude);
}
?>
--EXPECT--
float(3)
float(4)
float(5)
float(0)
float(0)
string(5) "Alice"
string(17) "alice@example.com"
int(25)
string(6) "Laptop"
float(999.99)
string(23) "High-performance laptop"
int(10)
string(5) "Mouse"
string(14) "No description"
string(9) "PHP Guide"
string(8) "John Doe"
float(54.989000000000004)
int(2)
float(40.7128)
float(-74.006)

@ -0,0 +1,92 @@
--TEST--
Generators - Yield keyword and generator functions
--SKIPIF--
<?php
echo "skip Generator syntax not supported in AOT";
?>
--FILE--
<?php
// Test basic generator
function range_generator($start, $end) {
for ($i = $start; $i <= $end; $i++) {
yield $i;
}
}
// Test generator with keys
function keyed_generator() {
yield 'a' => 1;
yield 'b' => 2;
yield 'c' => 3;
}
// Test generator sending values
function echo_generator() {
while (true) {
$value = yield;
echo "Received: " . $value . "\n";
}
}
// Test infinite generator
function infinite_sequence() {
$i = 1;
while (true) {
yield $i++;
if ($i > 5) break;
}
}
function main() {
// Test basic generator
echo "Basic generator:\n";
foreach (range_generator(1, 5) as $num) {
var_dump($num);
}
// Test keyed generator
echo "\nKeyed generator:\n";
foreach (keyed_generator() as $key => $value) {
echo $key . ": ";
var_dump($value);
}
// Test limited infinite generator
echo "\nInfinite sequence (limited):\n";
foreach (infinite_sequence() as $num) {
var_dump($num);
}
// Test generator object
$gen = range_generator(10, 12);
var_dump($gen->valid());
$gen->rewind();
var_dump($gen->valid());
var_dump($gen->current());
$gen->next();
var_dump($gen->current());
}
?>
--EXPECT--
Basic generator:
int(1)
int(2)
int(3)
int(4)
int(5)
Keyed generator:
a: int(1)
b: int(2)
c: int(3)
Infinite sequence (limited):
int(1)
int(2)
int(3)
int(4)
int(5)
bool(true)
bool(true)
int(1)
int(2)

@ -0,0 +1,97 @@
--TEST--
Traits - Basic functionality and method inheritance
--SKIPIF--
<?php
echo "skip Traits not yet supported in AOT";
?>
--FILE--
<?php
// Test basic trait usage
trait Greeting {
public function sayHello() {
return "Hello";
}
public function sayGoodbye() {
return "Goodbye";
}
}
class Person {
use Greeting;
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
// Test trait with abstract methods
trait Loggable {
abstract public function getTableName();
public function log($message) {
return "[" . $this->getTableName() . "] " . $message;
}
}
class User {
use Loggable;
public function getTableName() {
return "users";
}
}
// Test multiple traits
trait Timestamps {
public function getCreatedAt() {
return "2024-01-01 00:00:00";
}
public function getUpdatedAt() {
return "2024-01-02 00:00:00";
}
}
class Post {
use Greeting, Timestamps;
public function getTitle() {
return "Test Post";
}
}
function main() {
// Test basic trait
$person = new Person("John");
var_dump($person->sayHello());
var_dump($person->sayGoodbye());
var_dump($person->getName());
// Test trait with abstract method
$user = new User();
var_dump($user->log("User created"));
// Test multiple traits
$post = new Post();
var_dump($post->sayHello());
var_dump($post->getTitle());
var_dump($post->getCreatedAt());
var_dump($post->getUpdatedAt());
}
?>
--EXPECT--
string(5) "Hello"
string(7) "Goodbye"
string(4) "John"
string(22) "[users] User created"
string(5) "Hello"
string(9) "Test Post"
string(19) "2024-01-01 00:00:00"
string(19) "2024-01-02 00:00:00"
Loading…
Cancel
Save