feat(generator): add yield and generator support using Fiber backend

- Implement FiberGenerator trait with yield/yield from parsing
- Add generator detection and preparation logic in function compilation
- Generate TypePHP\FiberGenerator objects that implement Iterator
- Support yield expressions and statements with key-value pairs
- Enable yield from delegation with array and traversable forwarding
- Register fiber generator class entry during module initialization
- Update runtime initialization to use typephp_runtime_init
- Replace php_aot_ prefixed helpers with typephp_ prefixed versions
- Add comprehensive generator test suite covering various scenarios
- Document generator limitations and compatibility restrictions
- Mark dynamic PHP foreach over native generators as XFAIL case
pull/16/head
韩天峰 2 months ago
parent 3d8eb272a6
commit 2bad099e09
  1. 2
      docs/INCOMPATIBLE_PHP_FEATURES.md
  2. 2
      docs/PHP_INCOMPATIBILITY_CLASSIFICATION.md
  3. 21
      docs/YIELD_GENERATOR.md
  4. 4
      examples/lib-demo/cpp-src/exports.cc
  5. 8
      examples/minecraft-godot/cpp-src/typephp_world_api.cc
  6. 8
      examples/ocean-godot/cpp-src/typephp_ocean_api.cc
  7. 2
      examples/win32-hello/CXX_STD_CONFIG_GUIDE.md
  8. 20
      src/CompilerBase.php
  9. 1
      src/Entity/FunctionDef.php
  10. 189
      src/Generator/FiberGenerator.php
  11. 2
      src/Parser/AssignOpTrait.php
  12. 3
      src/Preprocessor.php
  13. 14
      src/Translator.php
  14. 2
      src/gen_stub.php
  15. 24
      tests/aot/generator/basic-yield-fallback.phpt
  16. 24
      tests/aot/generator/dynamic-foreach-native-generator.phpt
  17. 22
      tests/aot/generator/dynamic-generator-interop.inc
  18. 16
      tests/aot/generator/generators.phpt
  19. 30
      tests/aot/generator/method-private-property.phpt
  20. 33
      tests/aot/generator/method-yield-fallback.phpt
  21. 26
      tests/aot/generator/native-foreach-dynamic-generator.phpt
  22. 30
      tests/aot/generator/native-generator-foreach-combined.phpt
  23. 23
      tests/aot/generator/yield-from-array.phpt
  24. 29
      tests/aot/generator/yield-from-generator-return.phpt
  25. 20
      tests/aot/generator/yield-send.phpt
  26. 8
      tests/aot/loop/iterators.phpt

@ -13,13 +13,13 @@
## 声明与类型
- 不支持 `yield` / `yield from`
- 不支持可变变量 `$$var`
- 不支持 PHP 8.4 property hooks。
- 不支持闭包或箭头函数按引用返回。
- `__construct()` 不允许返回值。
- 参数默认值不允许出现在必填参数之前(`PHP`允许,但会直接丢弃此默认参数)。
- 不支持引用可变参数 `&...$args`
- 不支持在动态 PHP 脚本中使用 `foreach` 直接遍历 TypePHP Native generator 返回的 `TypePHP\FiberGenerator`
- 联合类型、交叉类型、`nullable` 类型在静态编译阶段按 `mixed/any` 处理,只保留运行时 type check。
- 局部变量类型一旦被静态推断为具体 native 类型,不支持在同一作用域内重新赋值为不兼容类型。
- attribute 参数不支持数组值和 `new` 表达式。

@ -77,13 +77,13 @@ These items should be documented with the exact boundary.
| Feature | Classification | Implementation Direction |
|---|---|---|
| `yield` / `yield from` | Pending | Lower generator functions to state machines and provide a Generator runtime object. |
| Variable variables (`$$var`) | Pending | Add a function-local symbol table mirror for dynamic locals, and disable or synchronize native locals that escape into dynamic lookup. |
| PHP 8.4 property hooks | Pending | Add parser and AST support, then lower property read/write paths to hook calls. |
| Closure or arrow function returning by reference | Pending | Closure metadata and wrappers must preserve return-by-reference and emit `ReturnRef`. |
| Closure and arrow function by-reference parameters | Pending | Closure arginfo must preserve by-reference parameters and call lowering must pass reference slots. |
| By-reference variadic parameters (`&...$args`) | Pending | Variadic storage must preserve references instead of copying values. |
| By-reference parameters with default values | Pending | Need PHP-compatible handling for omitted arguments using temporary default values while still binding references for passed arguments. |
| Dynamic PHP `foreach` over TypePHP Native generator | Pending / Complex | Requires Zend iterator handler integration or a userland wrapper so ZendVM can drive `TypePHP\FiberGenerator` exactly like native `Generator`. |
| Reference assignment from complex static property expressions | Pending | Static property reference targets need complete lowering and lifetime handling. |
| Dynamic calls automatically converting by-reference arguments | Pending | Runtime callable metadata or reflection can identify by-reference parameters and build reference arguments dynamically. |
| Calls with unpack plus trailing named arguments staying native | Pending | Normalize and reorder call arguments in IR before native-call selection. |

@ -0,0 +1,21 @@
# yield / generator 限制
TypePHP 的 generator 基于 PHP Fiber 运行。generator 函数或方法会返回 `TypePHP\FiberGenerator`,该对象实现 `Iterator`,但不是 PHP 内置 `Generator` 实例。
## 不支持
- 不支持声明返回类型为 `Generator`;请使用 `Iterator`、`Traversable`、`iterable`、`mixed`,或省略返回类型。
- 不支持按引用返回的 generator,例如 `function &gen() { yield 1; }`
- 不支持 generator 参数按引用传递。
- 不支持 generator 可变参数。
- 不支持 by-reference yield 语义。
- 不支持在动态 PHP 脚本中使用 `foreach` 直接遍历 TypePHP Native generator 返回的 `TypePHP\FiberGenerator`
- 不保证 `instanceof Generator`、`ReflectionGenerator`、`Generator` 内部实现细节与 Zend 原生 generator 兼容。
## 受限行为
- `yield from` 可以转发数组和 `Traversable` 的 key/value;委托对象是 generator 时可以读取其 return value。
- `yield from``send()`/`throw()` 委托透传仍属于受限场景,复杂协程式双向通信应避免依赖。
- TypePHP Native `foreach` 可以遍历动态 PHP 返回的 Zend 原生 generator;反向由 ZendVM `foreach` 驱动 TypePHP Native generator 暂不支持。
- generator 的执行依赖 Fiber;如果当前 PHP 运行环境禁用或缺失 Fiber,则无法运行。
- generator body 在 Fiber 内执行,析构、异常传播、force-close 与 Zend 原生 generator 可能存在边界差异。

@ -2,14 +2,14 @@
#include "../include/typephp_lib_demo.h"
#include <phpx.h>
extern "C" int php_aot_runtime_init(int argc, char **argv);
extern "C" int typephp_runtime_init(int argc, char **argv);
extern php::Int php_demo_add(php::Int a, php::Int b);
extern "C" TYPEPHP_LIB_DEMO_API int typephp_lib_demo_add(int a, int b)
{
char app_name[] = "typephp_lib_demo";
char *argv[] = {app_name, nullptr};
if (php_aot_runtime_init(1, argv) != 0) {
if (typephp_runtime_init(1, argv) != 0) {
return 0;
}
return static_cast<int>(php_demo_add(a, b));

@ -18,8 +18,8 @@ enum DemoBlockType {
static constexpr int DEMO_WATER_LEVEL_C = 4;
extern "C" int php_aot_runtime_init(int argc, char **argv);
extern "C" void php_aot_runtime_shutdown();
extern "C" int typephp_runtime_init(int argc, char **argv);
extern "C" void typephp_runtime_shutdown();
static bool g_typephp_world_initialized = false;
@ -31,7 +31,7 @@ static int typephp_world_ensure_runtime()
char app_name[] = "typephp_world";
char *argv[] = {app_name, nullptr};
if (php_aot_runtime_init(1, argv) != 0) {
if (typephp_runtime_init(1, argv) != 0) {
return 0;
}
@ -50,7 +50,7 @@ TYPEPHP_WORLD_API void typephp_world_shutdown()
return;
}
php_aot_runtime_shutdown();
typephp_runtime_shutdown();
g_typephp_world_initialized = false;
}

@ -6,8 +6,8 @@
#define TYPEPHP_OCEAN_API extern "C" __attribute__((visibility("default")))
#endif
extern "C" int php_aot_runtime_init(int argc, char **argv);
extern "C" void php_aot_runtime_shutdown();
extern "C" int typephp_runtime_init(int argc, char **argv);
extern "C" void typephp_runtime_shutdown();
static bool g_typephp_ocean_initialized = false;
@ -19,7 +19,7 @@ static int typephp_ocean_ensure_runtime()
char app_name[] = "typephp_ocean";
char *argv[] = {app_name, nullptr};
if (php_aot_runtime_init(1, argv) != 0) {
if (typephp_runtime_init(1, argv) != 0) {
return 0;
}
@ -37,7 +37,7 @@ TYPEPHP_OCEAN_API void typephp_ocean_shutdown()
if (!g_typephp_ocean_initialized) {
return;
}
php_aot_runtime_shutdown();
typephp_runtime_shutdown();
g_typephp_ocean_initialized = false;
}

@ -284,7 +284,7 @@ php bin/compiler.php project.yml --verbose
应该看到类似这样的输出:
```
Compiling main.cc...
Compiling typephp_main.cc...
g++ -std=c++17 -O0 -Wall ...
```

@ -25,6 +25,7 @@ use TypePhp\Exception\Skip;
use TypePhp\Exception\TestError;
use TypePhp\Generator\AnonClassGenerator;
use TypePhp\Generator\ClosureGenerator;
use TypePhp\Generator\FiberGenerator;
use TypePhp\Generator\PlaceHolderGenerator;
use TypePhp\Generator\PropertyPromotion;
use TypePhp\Generator\Utils;
@ -77,6 +78,7 @@ class CompilerBase implements PropertyAccessContext
use FuncCallOptimizer;
use AnonClassGenerator;
use ClosureGenerator;
use FiberGenerator;
use PlaceHolderGenerator;
use PropertyPromotion;
use MagicMethodDetector;
@ -244,7 +246,8 @@ class CompilerBase implements PropertyAccessContext
'phpx_big_int.h',
'phpx_big_float.h',
'phpx_decimal.h',
'php_aot_helper.h',
'typephp_helper.h',
'typephp_fiber_generator.h',
'phpx_std.h',
];
protected array $localHeaders = [];
@ -352,6 +355,7 @@ class CompilerBase implements PropertyAccessContext
protected ?ClassDef $classDef = null;
protected ?MethodDef $methodDef = null;
protected ?InterfaceDef $interfaceDef = null;
protected bool $inGeneratorBody = false;
protected FunctionContext $context;
protected array $superGlobalVars = [
'_GET' => self::TYPE_ARRAY,
@ -789,9 +793,9 @@ class CompilerBase implements PropertyAccessContext
case 'Expr_Exit':
return $this->parseExit($expr);
case 'Expr_Yield':
return $this->parseYieldExpr($expr);
case 'Expr_YieldFrom':
$this->fatalError($expr, 'The `' . $type . '` is not supported');
break;
return $this->parseYieldFromExpr($expr);
default:
abort($expr);
break;
@ -1599,7 +1603,13 @@ class CompilerBase implements PropertyAccessContext
$lines[] = $this->getComment($v, $class);
switch ($class) {
case 'Stmt_Expression':
$result = $this->parseExpr($v->expr) . ';';
if ($this->inGeneratorBody && $v->expr instanceof Expr\Yield_) {
$result = $this->parseYieldStmt($v->expr);
} elseif ($this->inGeneratorBody && $v->expr instanceof Expr\YieldFrom) {
$result = $this->parseYieldFromStmt($v->expr);
} else {
$result = $this->parseExpr($v->expr) . ';';
}
break;
case 'Stmt_Echo':
$result = $this->parseEcho($v);
@ -7013,7 +7023,7 @@ class CompilerBase implements PropertyAccessContext
$this->registerStaticPropertyRef($refVar, $class, $nativeProp, $info);
if ($info['kind'] === 'zval') {
$helper = $def->type === self::TYPE_FLOAT ? 'php_aot_static_float_ref' : 'php_aot_static_int_ref';
$helper = $def->type === self::TYPE_FLOAT ? 'typephp_static_float_ref' : 'typephp_static_int_ref';
return $helper . '(' . $refVar . ')';
}

@ -27,6 +27,7 @@ class FunctionDef
public bool $stub = false;
public bool $returnTypeUndeclared = false;
public bool $returnsByRef = false;
public bool $generator = false;
/**
* @var string 必须是带有命名空间的完整类名

@ -0,0 +1,189 @@
<?php
/**
* This file is part of TypePHP.
*
* @link https://www.swoole.com/
* @contact service@swoole.com
*/
namespace TypePhp\Generator;
use PhpParser\Node;
use PhpParser\Node\Expr\Yield_;
use PhpParser\Node\Expr\YieldFrom;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use TypePhp\Context\FunctionContext;
use TypePhp\Entity\FunctionDef;
trait FiberGenerator
{
protected function containsYield(Function_|ClassMethod $v): bool
{
return $this->containsYieldInNodes($v->stmts ?? []);
}
protected function containsYieldInNodes(array $nodes): bool
{
foreach ($nodes as $node) {
if ($node instanceof Node && $this->containsYieldInNode($node)) {
return true;
}
}
return false;
}
protected function containsYieldInNode(Node $node): bool
{
if ($node instanceof Yield_ || $node instanceof YieldFrom) {
return true;
}
if ($node instanceof Node\FunctionLike || $node instanceof Node\Stmt\ClassLike) {
return false;
}
foreach ($node->getSubNodeNames() as $name) {
$subNode = $node->{$name};
if ($subNode instanceof Node) {
if ($this->containsYieldInNode($subNode)) {
return true;
}
} elseif (is_array($subNode) && $this->containsYieldInNodes($subNode)) {
return true;
}
}
return false;
}
protected function prepareGeneratorFunction(Function_|ClassMethod $v, FunctionDef $functionDef): void
{
if ($v->byRef) {
$this->fatalError($v, 'Generators returning by reference are not supported yet');
}
foreach ($v->params as $param) {
if ($param->byRef || $param->variadic) {
$this->fatalError($param, 'Generators with by-reference or variadic parameters are not supported yet');
}
}
if ($functionDef->returnClass === 'Generator') {
$this->fatalError($v, 'Generator return type is not supported by TypePHP Fiber generators yet; use Iterator, Traversable, iterable, mixed, or omit the return type');
}
$functionDef->generator = true;
$functionDef->returnType = self::TYPE_VAR;
$functionDef->returnClass = '';
$functionDef->returnTypeCheck = null;
$functionDef->returnTypeStr = '';
$functionDef->returnTypeNode = null;
}
protected function parseYieldExpr(Yield_ $expr): string
{
if (!$this->inGeneratorBody) {
$this->fatalError($expr, 'The `Expr_Yield` is not supported outside generator functions');
}
return 'typephp_fiber_suspend(' . $this->genYieldPayload($expr) . ', nullptr)';
}
protected function parseYieldStmt(Yield_ $expr): string
{
$payload = $this->genYieldPayload($expr);
$closed = $this->genTmpVarName();
$this->addLocalVar($closed, self::TYPE_BOOL);
return $closed . ' = false;' . PHP_EOL
. $this->getIndent() . $closed . ' = typephp_fiber_yield(' . $payload . ');' . PHP_EOL
. $this->getIndent() . 'if (' . $closed . ') {' . PHP_EOL
. $this->getIndent() . ' return ' . self::VALUE_NULL . ';' . PHP_EOL
. $this->getIndent() . '}';
}
protected function parseYieldFromStmt(YieldFrom $expr): string
{
$closed = $this->genTmpVarName();
$this->addLocalVar($closed, self::TYPE_BOOL);
return $closed . ' = false;' . PHP_EOL
. $this->getIndent() . 'typephp_fiber_yield_from(' . $this->parseExprAsValue($expr->expr) . ', &' . $closed . ');' . PHP_EOL
. $this->getIndent() . 'if (' . $closed . ') {' . PHP_EOL
. $this->getIndent() . ' return ' . self::VALUE_NULL . ';' . PHP_EOL
. $this->getIndent() . '}';
}
protected function genYieldPayload(Yield_ $expr): string
{
$value = $expr->value ? $this->parseExprAsValue($expr->value) : self::VALUE_NULL;
if ($expr->key) {
$key = $this->parseExprAsValue($expr->key);
return 'php::Array(php::StdStrKeyMap{{"key", ' . $key . '}, {"value", ' . $value . '}, {"has_key", true}})';
}
return 'php::Array(php::StdStrKeyMap{{"value", ' . $value . '}, {"has_key", false}})';
}
protected function parseYieldFromExpr(YieldFrom $expr): string
{
if (!$this->inGeneratorBody) {
$this->fatalError($expr, 'The `Expr_YieldFrom` is not supported outside generator functions');
}
return 'typephp_fiber_yield_from(' . $this->parseExprAsValue($expr->expr) . ', nullptr)';
}
protected function genFiberGeneratorFunction(Function_|ClassMethod $v, FunctionDef $functionDef, string $nativeName): string
{
$functionDeclCode = self::TYPE_VAR . ' ' . self::PREFIX . $nativeName . '(';
if ($this->class) {
$functionDeclCode .= self::TYPE_OBJECT . ' &this_';
if ($functionDef->params) {
$functionDeclCode .= ', ';
}
}
$functionDeclCode .= $functionDef->params . ')';
$uses = [];
foreach ($functionDef->argInfoList as $argInfo) {
$uses[] = $argInfo->name;
}
$code = $functionDeclCode . ' {' . PHP_EOL;
$this->indentLevel++;
$closureVar = $this->genTmpVarName();
$code .= $this->getIndent() . 'php::ClosureFn ' . $closureVar . ' = []('
. 'INTERNAL_FUNCTION_PARAMETERS, '
. self::TYPE_OBJECT . ' &this_, '
. self::TYPE_ARGS . ' &vars_) -> ' . self::TYPE_VAR . ' {' . PHP_EOL;
$outerContext = $this->context;
$outerIndent = $this->indentLevel;
$outerInGeneratorBody = $this->inGeneratorBody;
$this->context = new FunctionContext();
$this->context->inClosure = true;
$this->inGeneratorBody = true;
$this->indentLevel++;
foreach ($functionDef->argInfoList as $i => $argInfo) {
$code .= $this->getIndent() . self::TYPE_VAR . ' ' . $argInfo->name . ' = vars_.get(' . $i . ');' . PHP_EOL;
$this->addArgument($argInfo->name, self::TYPE_VAR);
}
if ($this->class) {
$this->addArgument('this_', self::TYPE_OBJECT);
}
$body = '';
if ($v->stmts) {
$body = $this->parseStmts($v->stmts);
}
$body .= $this->getIndent() . 'return ' . self::VALUE_NULL . ';' . PHP_EOL;
$code .= $this->genScopeVarDecl() . $body;
$this->indentLevel = $outerIndent;
$this->inGeneratorBody = $outerInGeneratorBody;
$this->context = $outerContext;
$code .= $this->getIndent() . '};' . PHP_EOL;
$args = $uses ? '{ ' . implode(', ', $uses) . ' }' : '{}';
$closureExpr = $this->class
? 'php::newClosure(' . $closureVar . ', ' . $args . ', this_)'
: 'php::newClosure(' . $closureVar . ', ' . $args . ')';
$code .= $this->getIndent() . 'return php::newObject(typephp_fiber_generator_ce, {' . $closureExpr . '});' . PHP_EOL;
$this->indentLevel--;
$code .= '}' . PHP_EOL;
return $code;
}
}

@ -500,7 +500,7 @@ trait AssignOpTrait
$var = $this->parseWritableIdentifier($node->var);
if (!$this->isNativePropertyTypedValue($node->var)) {
$helper = $def->type === self::TYPE_FLOAT ? 'php_aot_static_float_ref' : 'php_aot_static_int_ref';
$helper = $def->type === self::TYPE_FLOAT ? 'typephp_static_float_ref' : 'typephp_static_int_ref';
$var = $helper . '(' . $var . '.unwrap_ptr())';
}

@ -443,6 +443,9 @@ class Preprocessor extends CompilerBase
$functionDef->stub = $this->stubFile;
$functionDef->returnTypeUndeclared = $v->returnType === null;
$functionDef->returnsByRef = $v->byRef;
if ($this->containsYield($v)) {
$this->prepareGeneratorFunction($v, $functionDef);
}
if ($v->returnType instanceof NullableType || $v->returnType instanceof UnionType || $v->returnType instanceof IntersectionType) {
$typeInfo = $this->buildTypeCheckFromNode($v->returnType);

@ -1015,6 +1015,7 @@ CODE;
$code .= 'PHP_MINIT_FUNCTION(' . $this->getModuleName() . ') {' . PHP_EOL;
$code .= 'zend_try {' . PHP_EOL;
$code .= '// class/interface class entries' . PHP_EOL;
$code .= 'typephp_register_fiber_generator_class();' . PHP_EOL;
$code .= $this->genClassPropertyInit() . PHP_EOL;
$code .= '// register symbols' . PHP_EOL;
@ -1344,9 +1345,11 @@ CODE;
{
$job = $this->maxJob;
$sourceFiles[] = $this->getPhpxDir() . '/src/misc/typephp_fiber_generator.cc';
// embed 需要 main 函数,以及 cli 的内置函数定义
if ($this->isBuildModeEmbed()) {
$sourceFiles[] = $this->getPhpxDir() . '/src/misc/main.cc';
$sourceFiles[] = $this->getPhpxDir() . '/src/misc/typephp_main.cc';
}
if ($this->isBuildModeBin()) {
@ -1640,7 +1643,7 @@ CODE;
$userDefines = $this->userDefines;
if ($this->isBuildModeLib()) {
$userDefines[] = 'PHPX_NO_MAIN=1';
$userDefines[] = 'TYPEPHP_NO_MAIN=1';
}
return [
@ -1822,6 +1825,7 @@ CODE;
public function genFunctionDeclaration(string $file): void
{
$code = '#include <phpx.h>' . PHP_EOL;
$code .= '#include <typephp_fiber_generator.h>' . PHP_EOL;
// 函数的默认值可能会使用字符串字面量,需要提前声明
if ($this->literalStrings) {
@ -3414,6 +3418,12 @@ CODE;
}
}
if ($this->functionDef->generator) {
$code = $this->genFiberGeneratorFunction($v, $this->functionDef, $name);
$this->resetFunction();
return $code;
}
// Build SSA/e-SSA analysis for this function
if ($v->stmts) {
$oriLocalVars = $this->context->localVars;

@ -3796,7 +3796,7 @@ class ClassInfo {
$code .= "\n\tstatic zend_object_handlers class_object_handlers;";
$code .= "\n\tmemcpy(&class_object_handlers, class_entry->default_object_handlers, sizeof(zend_object_handlers));";
$code .= "\n\tclass_object_handlers.unset_property = php_aot_unset_typed_property;";
$code .= "\n\tclass_object_handlers.unset_property = typephp_unset_typed_property;";
$code .= "\n\tclass_entry->default_object_handlers = &class_object_handlers;";
$code .= "\n";

@ -0,0 +1,24 @@
--TEST--
generator functions via Fiber iterator
--FILE--
<?php
function gen_values(int $start): iterable
{
yield 'a' => $start;
yield 'b' => $start + 1;
}
function main(): void
{
$gen = gen_values(10);
var_dump($gen instanceof Iterator);
foreach ($gen as $key => $value) {
var_dump($key . ':' . $value);
}
}
?>
--EXPECT--
bool(true)
string(4) "a:10"
string(4) "b:11"

@ -0,0 +1,24 @@
--TEST--
dynamic PHP foreach consumes native TypePHP generator
--XFAIL--
Dynamic PHP foreach over TypePHP FiberGenerator requires Zend iterator handler integration.
--FILE--
<?php
function native_values(): iterable
{
yield 'native-a' => 100;
yield 'native-b' => 200;
}
function main(): void
{
require __DIR__ . '/dynamic-generator-interop.inc';
foreach (dynamic_collect_iterable(native_values()) as $line) {
echo $line, "\n";
}
}
?>
--EXPECT--
native-a:100
native-b:200

@ -0,0 +1,22 @@
<?php
function dynamic_yield_values(): iterable
{
yield 'dyn-a' => 11;
yield 'dyn-b' => 22;
}
function dynamic_yield_from_values(): iterable
{
yield 'dyn-start' => 0;
yield from dynamic_yield_values();
yield 'dyn-end' => 33;
}
function dynamic_collect_iterable(iterable $iterable): array
{
$result = [];
foreach ($iterable as $key => $value) {
$result[] = $key . ':' . $value;
}
return $result;
}

@ -1,9 +1,5 @@
--TEST--
Generators - Yield keyword and generator functions
--SKIPIF--
<?php
exit("skip: Generator syntax not supported in AOT");
?>
--FILE--
<?php
// Test basic generator
@ -20,14 +16,6 @@ function keyed_generator() {
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;
@ -88,5 +76,5 @@ int(4)
int(5)
bool(true)
bool(true)
int(1)
int(2)
int(10)
int(11)

@ -0,0 +1,30 @@
--TEST--
generator methods can access private properties
--FILE--
<?php
class PrivateGeneratorBox implements IteratorAggregate
{
private array $items;
public function __construct(array $items)
{
$this->items = $items;
}
public function getIterator(): Traversable
{
foreach ($this->items as $item) {
yield $item;
}
}
}
function main(): void
{
foreach (new PrivateGeneratorBox([1]) as $value) {
echo $value, "\n";
}
}
?>
--EXPECTF--
1

@ -0,0 +1,33 @@
--TEST--
generator methods via Fiber iterator
--FILE--
<?php
class GeneratorMethodBox implements IteratorAggregate
{
private array $items;
public function __construct(array $items)
{
$this->items = $items;
}
public function getIterator(): Traversable
{
foreach ($this->items as $key => $value) {
yield $key => strtoupper($value);
}
}
}
function main(): void
{
$box = new GeneratorMethodBox(['first' => 'alpha', 'second' => 'beta']);
foreach ($box as $key => $value) {
echo $key, '=', $value, "\n";
}
}
?>
--EXPECT--
first=ALPHA
second=BETA

@ -0,0 +1,26 @@
--TEST--
native foreach consumes dynamic PHP generator
--FILE--
<?php
function main(): void
{
require __DIR__ . '/dynamic-generator-interop.inc';
foreach (dynamic_yield_values() as $key => $value) {
echo $key, ':', $value, "\n";
}
echo "-- yield from --\n";
foreach (dynamic_yield_from_values() as $key => $value) {
echo $key, ':', $value, "\n";
}
}
?>
--EXPECT--
dyn-a:11
dyn-b:22
-- yield from --
dyn-start:0
dyn-a:11
dyn-b:22
dyn-end:33

@ -0,0 +1,30 @@
--TEST--
native generator and native foreach interoperate
--FILE--
<?php
function native_child(): iterable
{
yield 'child-a' => 1;
yield 'child-b' => 2;
return 3;
}
function native_parent(): iterable
{
yield 'parent-start' => 0;
$ret = yield from native_child();
yield 'parent-ret' => $ret;
}
function main(): void
{
foreach (native_parent() as $key => $value) {
echo $key, ':', $value, "\n";
}
}
?>
--EXPECT--
parent-start:0
child-a:1
child-b:2
parent-ret:3

@ -0,0 +1,23 @@
--TEST--
yield from forwards array keys and values
--FILE--
<?php
function gen_from_array(): iterable
{
yield 'start' => 0;
yield from ['a' => 1, 'b' => 2];
yield 'end' => 3;
}
function main(): void
{
foreach (gen_from_array() as $key => $value) {
echo $key, ':', $value, "\n";
}
}
?>
--EXPECT--
start:0
a:1
b:2
end:3

@ -0,0 +1,29 @@
--TEST--
yield from forwards delegated generator values and returns its result
--FILE--
<?php
function child_gen(): iterable
{
yield 'x' => 10;
yield 'y' => 20;
return 30;
}
function parent_gen(): iterable
{
$result = yield from child_gen();
yield 'result' => $result;
}
function main(): void
{
$gen = parent_gen();
foreach ($gen as $key => $value) {
echo $key, ':', $value, "\n";
}
}
?>
--EXPECT--
x:10
y:20
result:30

@ -0,0 +1,20 @@
--TEST--
yield expression receives send value
--FILE--
<?php
function gen_send(): iterable
{
$value = yield 1;
yield $value;
}
function main(): void
{
$gen = gen_send();
var_dump($gen->current());
var_dump($gen->send(42));
}
?>
--EXPECT--
int(1)
int(42)

@ -1,9 +1,5 @@
--TEST--
Iterable and Iterator - Custom iteration with Traversable
--SKIPIF--
<?php
exit("skip: Generator syntax not supported in AOT");
?>
--FILE--
<?php
// Test implementing Iterator interface
@ -70,7 +66,7 @@ class Range implements IteratorAggregate {
}
// Test filtering iterator
class FilterIterator implements Iterator {
class NumberFilterIterator implements Iterator {
private Iterator $iterator;
private mixed $filterValue;
@ -137,7 +133,7 @@ function main() {
// Test FilterIterator
echo "\nFilterIterator:\n";
$baseIterator = new NumberIterator(1, 10);
$filterIterator = new FilterIterator($baseIterator, 5);
$filterIterator = new NumberFilterIterator($baseIterator, 5);
foreach ($filterIterator as $value) {
echo "{$value}\n";
}

Loading…
Cancel
Save