feat(aot): 添加对表达式结果的通用方法调用支持

- 实现了函数调用、方法调用、静态调用和属性获取后的通用方法调用
- 新增 stream_cast 伪函数用于流类型转换测试
- 支持数组方法链式调用和可变操作
- 添加表达式上下文中的流转换功能
- 扩展了对内部函数反射返回类型的处理
- 优化了数组操作的返回值一致性
- 完善了通用方法调用的错误处理机制
pull/1/head
韩天峰 3 months ago
parent 6309346c9a
commit 12d44084ec
  1. 7
      phpunit/code/universal-method-mutating-expr.php
  2. 7
      phpunit/code/universal-method-stream-cast-no-arg.php
  3. 6
      phpunit/code/universal-method-stream-unknown-method.php
  4. 8
      tests/aot/array_method/3.phpt
  5. 49
      tests/aot/stream_method/stream_cast.phpt
  6. 114
      tests/aot/universal_method_expr.phpt

@ -0,0 +1,7 @@
<?php
function main()
{
$arr = [];
$arr->push(1);
($arr->slice(0, 1))->push(2);
}

@ -0,0 +1,7 @@
<?php
function main()
{
$tmpfile = tempnam(sys_get_temp_dir(), 'aot');
$fp = fopen($tmpfile, 'w');
stream_cast()->write('data');
}

@ -0,0 +1,6 @@
<?php
function main()
{
$tmpfile = tempnam(sys_get_temp_dir(), 'aot');
stream_cast(fopen($tmpfile, 'w'))->unknownMethod();
}

@ -268,11 +268,11 @@ bool(true)
string(1) "x"
string(1) "z"
string(15) "one, two, three"
int(4)
string(1) "c"
int(2)
NULL
int(3)
string(1) "b"
int(1)
int(2)
string(1) "d"
int(1)
int(1)
@ -297,4 +297,4 @@ string(27) "{"name":"test","value":123}"
int(1)
float(1)
bool(true)
done
done

@ -0,0 +1,49 @@
--TEST--
stream_cast() pseudo-function for stream type casting
--FILE--
<?php
function main()
{
require __DIR__ . '/../../../src/Assert.php';
// Test 1: stream_cast identity — on an already-inferred stream variable
$tmpfile = tempnam(sys_get_temp_dir(), 'aot');
$fp = fopen($tmpfile, 'w+');
$fp->write("hello world");
$fp->seek(0);
$data = stream_cast($fp)->read(5);
var_dump($data);
$fp->close();
// Test 2: stream_cast on array elements where type isn't known
$pipes = [];
$pipes[0] = fopen($tmpfile, 'w');
$w = stream_cast($pipes[0]);
$w->write("test data from pipe");
$w->close();
$pipes[1] = fopen($tmpfile, 'r');
$r = stream_cast($pipes[1]);
$data2 = $r->read(1024);
var_dump($data2);
$r->close();
// Test 3: stream_cast in expression context (method chaining)
$fp2 = fopen($tmpfile, 'w');
stream_cast($fp2)->write("chain test");
stream_cast($fp2)->close();
$fp3 = fopen($tmpfile, 'r');
var_dump(stream_cast($fp3)->read(10));
stream_cast($fp3)->close();
unlink($tmpfile);
echo "OK\n";
}
?>
--EXPECT--
string(5) "hello"
string(19) "test data from pipe"
string(10) "chain test"
OK

@ -0,0 +1,114 @@
--TEST--
Universal method call on expression results (FuncCall, MethodCall, StaticCall, PropertyFetch)
--FILE--
<?php
// --- Helpers: functions and classes ---
function getString(): string
{
return "hello world";
}
function getInt(): int
{
return 42;
}
function getFloat(): float
{
return 3.14;
}
function getArray(): array
{
return [1, 2, 3, 4, 5];
}
class Helper {
public string $name = "hello";
public function getName(): string
{
return "test";
}
public function getInt(): int
{
return 100;
}
public static function staticName(): string
{
return "STATIC";
}
public static function staticArray(): array
{
return ["a", "b", "c"];
}
}
function main()
{
require __DIR__ . '/../../src/Assert.php';
// --- Test 1: FuncCall -> universal method ---
Assert::eq(getString()->length(), 11);
Assert::eq(getString()->upper(), "HELLO WORLD");
Assert::eq(getString()->contains("world"), true);
Assert::eq(getInt()->toFloat(), 42.0);
Assert::eq(getFloat()->toInt(), 3);
Assert::eq(getArray()->count(), 5);
Assert::eq(getArray()->isEmpty(), false);
// --- Test 2: MethodCall on typed object -> universal method ---
$obj = new Helper();
Assert::eq($obj->getName()->length(), 4);
Assert::eq($obj->getName()->upper(), "TEST");
Assert::eq($obj->getInt()->toFloat(), 100.0);
// --- Test 3: StaticCall -> universal method ---
Assert::eq(Helper::staticName()->length(), 6);
Assert::eq(Helper::staticName()->lower(), "static");
Assert::eq(Helper::staticArray()->count(), 3);
Assert::eq(Helper::staticArray()->isEmpty(), false);
// --- Test 4: PropertyFetch on typed property -> universal method ---
Assert::eq($obj->name->length(), 5);
Assert::eq($obj->name->upper(), "HELLO");
// --- Test 5: Nested chains ---
Assert::eq(Helper::staticName()->upper()->length(), 6);
// --- Test 6: Typed object property with string method chaining (partial assertion on contains) ---
Assert::eq($obj->getName()->contains("es"), true);
// --- Test 7: Internal function with reflection-based return type ---
// random_bytes() returns string, so base64Encode() should work
Assert::greaterThan(random_bytes(128)->base64Encode()->length(), 0);
// --- Test 8: Array method chaining ---
$arr = [3, 1, 2];
// sort() is mutating, returns the array itself
$arr->sort();
Assert::eq($arr->get(0), 1);
// reverse() returns new array, can chain; sorted=[1,2,3], reversed=[3,2,1]
Assert::eq($arr->reverse()->get(0), 3);
Assert::eq($arr->reverse()->count(), 3);
// double chain: reverse()->reverse() returns original order [1,2,3]
Assert::eq($arr->reverse()->reverse()->get(0), 1);
// slice() returns new array, chain with count()
Assert::eq($arr->slice(0, 2)->count(), 2);
// values() returns new array
Assert::eq($arr->values()->count(), 3);
// keys() returns new array, chain further
Assert::eq($arr->keys()->count(), 3);
// merge() returns new array
Assert::eq($arr->merge([4, 5])->count(), 5);
echo "OK\n";
}
?>
--EXPECT--
OK
Loading…
Cancel
Save