Merge pull request '修复引用相关问题' (#17) from fix-ref into master

Reviewed-on: #17
pull/20/head
韩天峰 1 month ago
commit 44fdc0b387
  1. 76
      src/CompilerBase.php
  2. 5
      src/Generator/Symbol.php
  3. 3
      src/Parser/AssignOpTrait.php
  4. 16
      src/Parser/MethodCallTrait.php
  5. 28
      src/Parser/PropertyAccessTrait.php
  6. 6
      src/TypeSystem/CompositeTypeCheckerTrait.php
  7. 52
      tests/compiler/ref/dynamic-return-reference-chain.phpt
  8. 34
      tests/compiler/ref/function-return-reference-chain.phpt
  9. 33
      tests/compiler/ref/method-return-reference-chain.phpt
  10. 33
      tests/compiler/ref/static-return-reference-chain.phpt
  11. 39
      tests/compiler/ref/static-return-reference-lsb.phpt
  12. 44
      tests/compiler/static/static-call-byref-arg.phpt
  13. 38
      tests/compiler/static/static-call-byref-undefined-var.phpt
  14. 34
      tests/compiler/static/static-prop-assign-ref-late-static-binding.phpt
  15. 41
      tests/compiler/static/static-prop-assign-ref-native.phpt
  16. 36
      tests/compiler/static/static-prop-assign-ref-parent.phpt
  17. 25
      tests/compiler/static/static-prop-assign-ref-type-error.phpt
  18. 40
      tests/compiler/static/static-prop-assign-ref.phpt

@ -1812,12 +1812,85 @@ class CompilerBase implements PropertyAccessContext
$this->fatalError($arg, 'Only string literals or `ClassName::class` constant are supported');
}
/**
* Resolve whether a call returns by reference. A null result means that
* dispatch is dynamic and must be checked at runtime.
*/
protected function resolveRefReturningCall(Node $expr): ?bool
{
if ($expr instanceof Expr\FuncCall && ($this->isNameExpr($expr->name) || $this->isFullNameExpr($expr->name))) {
$name = $this->parseIdentifier($expr->name);
$function = $this->findNativeFunction($name);
if ($function !== false) {
return $this->getFunction($function)->returnsByRef;
}
$reflection = \TypePhp\Resolver\Reflection::getFunction(ltrim($this->getNamespacedFuncName($name), '\\'));
return $reflection?->returnsReference();
}
if ($expr instanceof Expr\FuncCall) {
return null;
}
if ($expr instanceof Expr\MethodCall && $this->isNamedMethod($expr->name) && $this->isVarExpr($expr->var)) {
$object = $this->parseIdentifier($expr->var);
$method = $this->parseIdentifier($expr->name);
if ($object === 'this_') {
$class = $this->getFullClassName();
} elseif (isset($this->context->objects[$object])) {
$class = $this->context->stableObjects[$object] ?? $this->context->objects[$object];
} else {
return null;
}
$function = $this->getNativeMethod($expr, $class, $method, false);
if ($function !== false) {
return $this->getFunction($function)->returnsByRef;
}
return null;
}
if ($expr instanceof Expr\MethodCall) {
return null;
}
if ($expr instanceof Expr\StaticCall && ($this->isNameExpr($expr->class) || $this->isFullNameExpr($expr->class)) && $this->isIdExpr($expr->name)) {
$class = $this->parseIdentifier($expr->class);
if ($class === 'self') {
$class = $this->getFullClassName();
} elseif ($class === 'parent') {
if (!$this->classDef || !$this->classDef->extends) {
return false;
}
$class = $this->classDef->extends;
} elseif ($class === 'static') {
if (!$this->classDef) {
return null;
}
$class = $this->getFullClassName();
} else {
$class = $this->getNamespacedClassName($class);
}
$method = $this->parseIdentifier($expr->name);
$function = $this->getNativeMethod($expr, $class, $method, false);
if ($function !== false) {
return $this->getFunction($function)->returnsByRef;
}
return null;
}
if ($expr instanceof Expr\StaticCall) {
return null;
}
return false;
}
protected function parseReturn(Node\Stmt\Return_ $v): string
{
if ($this->functionDef->returnsByRef) {
if ($v->expr === null) {
return 'return ' . Type::REF . '{};';
}
if ($v->expr instanceof CallLike) {
$returnsByRef = $this->resolveRefReturningCall($v->expr);
if ($returnsByRef !== false) {
return 'return php::toReferenceExact(' . $this->parseExpr($v->expr) . ');';
}
}
if (!$this->isVarExpr($v->expr)
&& !$this->isPropertyFetch($v->expr)
&& !$this->isStaticPropertyFetch($v->expr)
@ -1849,6 +1922,9 @@ class CompilerBase implements PropertyAccessContext
if ($this->isPropertyFetch($v->expr)) {
return 'return ' . $this->emitDynamicPropertyFetchRef($v->expr, $v) . ';';
}
if ($this->isStaticPropertyFetch($v->expr)) {
return 'return ' . $this->emitStaticPropertyFetchRef($v->expr, $v) . ';';
}
return 'return ' . $this->parseChainedExpr($v->expr, self::OP_REFVAL) . ';';
}
if ($v->expr === null) {

@ -17,6 +17,11 @@ class Symbol
return 'php::getStaticProperty';
}
public static function getStaticPropertyRef(): string
{
return 'php::getStaticPropertyRef';
}
public static function setStaticProperty(): string
{
return 'php::setStaticProperty';

@ -774,6 +774,9 @@ trait AssignOpTrait
} elseif ($this->isPropertyFetch($expr->expr)) {
$left = $this->parseIdentifier($expr->var);
$rightExpr = $tmpVar . ' = ' . $this->emitDynamicPropertyFetchRef($expr->expr, $expr);
} elseif ($this->isStaticPropertyFetch($expr->expr)) {
$left = $this->parseIdentifier($expr->var);
$rightExpr = $tmpVar . ' = ' . $this->emitStaticPropertyFetchRef($expr->expr, $expr);
} elseif ($this->isArrayDimFetch($expr->expr)) {
$left = $this->parseIdentifier($expr->var);
$array = $this->parseWritableIdentifier($expr->expr->var);

@ -252,6 +252,7 @@ trait MethodCallTrait
$this->guardAbstractMethod($parentClass, $method, $expr);
$methodPtr = $this->getMethodPtr($parentClass, $method);
} else {
$method = '';
// parent:: is bound to the lexical parent class, not the runtime
// object's parent. Look the method up on that class, then invoke it
// through this_ so Zend receives the current call scope.
@ -261,7 +262,8 @@ trait MethodCallTrait
if (empty($expr->args)) {
return 'this_.call(' . $methodPtr . ')';
}
return 'this_.call(' . $methodPtr . ', ' . $this->parseCallArgs($expr->args) . ')';
// 传入方法名与父类名,以便在按引用参数检测时解析方法签名
return 'this_.call(' . $methodPtr . ', ' . $this->parseCallArgs($expr->args, $method, $parentClass) . ')';
}
@ -453,6 +455,8 @@ trait MethodCallTrait
{
$self = false;
$callScope = [];
$rtFunc = '';
$rtClass = '';
$class = $this->parseIdentifier($expr->class);
// parent::$method() still has a lexical parent class even when the
@ -475,13 +479,17 @@ trait MethodCallTrait
}
$placeHolder = $fn;
} elseif ($this->isNameExpr($expr->class) and $class === 'static') {
$method = $this->parseIdentifier($expr->name);
$methodPtr = $this->identifierToStr($expr->name, literal: true);
$fn = Symbol::getCalledCe() . ', php::getMethod(' . Symbol::getCalledCe() . ', ' . $methodPtr . ')';
$this->context->beforeStmtLines[] = $this->formatCppLineComment(
'Static Method Call: ',
'static::' . $this->parseIdentifier($expr->name) . '()'
'static::' . $method . '()'
);
$placeHolder = $this->genArray([Symbol::getCalledClass(), $methodPtr]);
// 用于在按引用参数检测时解析方法签名(late static binding 在当前类层级中解析)
$rtFunc = $method;
$rtClass = $this->getNamespacedClassName($this->class);
} elseif ($this->isNameExpr($expr->class)) {
if ($class === 'self') {
$class = $this->class;
@ -493,6 +501,8 @@ trait MethodCallTrait
_do_call:
$method = $this->parseIdentifier($expr->name);
$rtFunc = $method;
$rtClass = $class;
$dynamicCall = false;
$this->context->beforeStmtLines[] = $this->formatCppLineComment(
'Static Method Call: ',
@ -556,7 +566,7 @@ trait MethodCallTrait
return $call . '(' . $fn . ')';
}
try {
return $this->genRuntimeFunctionCall($fn, $expr->args);
return $this->genRuntimeFunctionCall($fn, $expr->args, $rtFunc, $rtClass);
} catch (PlaceHolder) {
return $this->genPlaceHolder($placeHolder);
}

@ -201,6 +201,29 @@ trait PropertyAccessTrait
return $objectExpr . '.attrRef(' . $this->identifierToStr($expr->name) . ')';
}
protected function emitStaticPropertyFetchRef(Expr\StaticPropertyFetch $expr, NodeAbstract $errorNode): string
{
$resolution = $this->resolveNativeStaticPropertyFetch($expr);
if ($this->isIdExpr($expr->name)) {
$this->assertPropertySetVisibility($expr);
}
if ($resolution !== null) {
$property = $this->identifierToStr($expr->name, literal: true);
if ($resolution->class !== null) {
$classPtr = $this->getClassEntryPtr($resolution->class);
return Symbol::getStaticPropertyRef() . '(' . $classPtr . ', ' . $property . ')';
}
if ($resolution->expression !== null) {
// Dynamic target, e.g. `self` resolved through the called class inside a trait.
return Symbol::getStaticPropertyRef() . '(' . Symbol::getCalledCe() . ', ' . $property . ')';
}
}
// Fully dynamic path: `static` keyword, dynamic class name, or dynamic property name.
return $this->parseDynamicStaticPropertyFetch($expr, true);
}
protected function resolveNativeStaticPropertyFetch(Expr\StaticPropertyFetch $expr): ?StaticPropertyFetchResolution
{
@ -326,7 +349,7 @@ trait PropertyAccessTrait
* object. Materialising both operands preserves PHP's left-to-right
* evaluation order and avoids ambiguous C++ overload resolution for Var.
*/
private function parseDynamicStaticPropertyFetch(Expr\StaticPropertyFetch $expr): string
private function parseDynamicStaticPropertyFetch(Expr\StaticPropertyFetch $expr, bool $reference = false): string
{
$classValue = $this->getDynamicStaticClassValue($expr->class);
$propertyValue = $this->identifierToStr($expr->name, literal: true);
@ -337,7 +360,8 @@ trait PropertyAccessTrait
$this->context->beforeStmtLines[] = $propertyVar . ' = ' . $propertyValue . ';';
$className = '(' . $classVar . '.isObject() ? php::fn::get_class(' . $classVar . ') : php::toString(' . $classVar . '))';
return Symbol::getStaticProperty() . '(' . $className . ', php::toString(' . $propertyVar . '))';
$helper = $reference ? Symbol::getStaticPropertyRef() : Symbol::getStaticProperty();
return $helper . '(' . $className . ', php::toString(' . $propertyVar . '))';
}
private function getDynamicStaticClassValue(NodeAbstract $class): string

@ -35,7 +35,11 @@ trait CompositeTypeCheckerTrait
// TYPE_VAR means that the expression is dynamic or its result cannot
// be represented by the current scalar type system. It must retain the
// runtime type check.
if ($this->detectTypeOfExpr($value) === Type::VAR && !$this->isNullExpr($value)) {
// A reference (TYPE_REF) is a Variant reference whose concrete type is
// only known at runtime (e.g. an undefined variable auto-created by a
// by-reference argument), so it is treated the same way.
$valueType = $this->detectTypeOfExpr($value);
if (($valueType === Type::VAR || $valueType === Type::REF) && !$this->isNullExpr($value)) {
return self::COMPOSITE_TYPE_UNKNOWN;
}

@ -0,0 +1,52 @@
--TEST--
Reference-returning functions can forward dynamic and chained calls
--FILE--
<?php
function &dynamic_source(): mixed
{
static $value = 1;
return $value;
}
function &dynamic_forward(string $function): mixed
{
return $function();
}
class DynamicRefBox
{
public int $value = 2;
public function &getValue(): mixed
{
return $this->value;
}
}
class DynamicRefFactory
{
public function create(): DynamicRefBox
{
return new DynamicRefBox();
}
public function &forward(): mixed
{
return $this->create()->getValue();
}
}
function main(): void
{
$dynamic = &dynamic_forward('dynamic_source');
$dynamic = 10;
var_dump(dynamic_source());
$factory = new DynamicRefFactory();
$chained = &$factory->forward();
var_dump($chained);
}
?>
--EXPECT--
int(10)
int(2)

@ -0,0 +1,34 @@
--TEST--
Function returning by reference can forward another by-reference call
--FILE--
<?php
function main()
{
$value1 = test1();
var_dump($value1);
$value2 = &test1();
var_dump($value2);
$value2 = 0;
$value3 = test1();
var_dump($value3);
}
function &test1()
{
return test2();
}
function &test2()
{
global $value;
$value++;
return $value;
}
// main();
?>
--EXPECT--
int(1)
int(2)
int(1)

@ -0,0 +1,33 @@
--TEST--
Method returning by reference can forward another by-reference method call
--FILE--
<?php
class Test
{
private $value = 1;
public function &getValue()
{
return $this->value;
}
public function &getRefValue()
{
return $this->getValue();
}
}
function main()
{
$test = new Test;
var_dump($test->getRefValue());
$ref = &$test->getRefValue();
$ref = 2;
var_dump($test->getValue());
}
// main();
?>
--EXPECT--
int(1)
int(2)

@ -0,0 +1,33 @@
--TEST--
Static method returning by reference can forward another by-reference static call
--FILE--
<?php
class Counter
{
private static $value = 0;
public static function &next()
{
self::$value++;
return self::$value;
}
public static function &current()
{
return self::next();
}
}
function main()
{
var_dump(Counter::current());
$ref = &Counter::current();
$ref = 10;
var_dump(Counter::next());
}
// main();
?>
--EXPECT--
int(1)
int(11)

@ -0,0 +1,39 @@
--TEST--
Reference-returning static method forwards a late-static-bound reference
--FILE--
<?php
class RefLsbBase
{
protected static int $value = 1;
protected static function &valueRef(): mixed
{
return static::$value;
}
public static function &forward(): mixed
{
return static::valueRef();
}
public static function value(): int
{
return static::$value;
}
}
class RefLsbChild extends RefLsbBase
{
protected static int $value = 2;
}
function main(): void
{
$value = &RefLsbChild::forward();
$value = 20;
var_dump(RefLsbChild::value(), RefLsbBase::value());
}
?>
--EXPECT--
int(20)
int(1)

@ -0,0 +1,44 @@
--TEST--
Static method calls (self / static / class name / parent) with by-reference args
--FILE--
<?php
class P
{
public static function test(?string &$value): void
{
$value = 'test';
}
}
class TestClass extends P
{
public static function abc()
{
self::test($v1);
var_dump($v1);
static::test($v2);
var_dump($v2);
TestClass::test($v3);
var_dump($v3);
parent::test($v4);
var_dump($v4);
static::test(value: $v5);
var_dump($v5);
parent::test(value: $v6);
var_dump($v6);
}
}
function main()
{
TestClass::abc();
}
?>
--EXPECT--
string(4) "test"
string(4) "test"
string(4) "test"
string(4) "test"
string(4) "test"
string(4) "test"

@ -0,0 +1,38 @@
--TEST--
Static method call passing an undefined variable by reference (late static binding)
--FILE--
<?php
class Test
{
public static function getName(?string &$name)
{
$name = 'test';
}
public static function run()
{
// $fileName is undefined; passing it by reference must auto-create it
// instead of raising an "Undefined variable" fatal error.
static::getName($fileName);
var_dump($fileName);
Test2::dump($fileName);
}
}
class Test2
{
public static function dump(?string $value)
{
var_dump($value);
}
}
function main()
{
Test::run();
}
?>
--EXPECT--
string(4) "test"
string(4) "test"

@ -0,0 +1,34 @@
--TEST--
Assign by reference to late-static-bound property resolves to called class
--FILE--
<?php
use native_types;
class Base {
protected static int $value = 1;
public static function bump(): void {
$ref = &static::$value;
$ref = 99;
}
public static function show(): void {
var_dump(static::$value);
}
}
class Child extends Base {
protected static int $value = 2;
}
function main(): void {
Base::show();
Child::bump();
Child::show();
Base::show();
}
?>
--EXPECT--
int(1)
int(99)
int(1)

@ -0,0 +1,41 @@
--TEST--
Assign by reference to native typed static property (self / static / class name)
--FILE--
<?php
use native_types;
class Test
{
private static int $value = 123;
public static function abc(): void
{
$a = &self::$value;
var_dump($a);
$a = 456;
var_dump(self::$value);
$b = &static::$value;
var_dump($b);
$b = 789;
var_dump(static::$value);
$c = &Test::$value;
var_dump($c);
$c = 1000;
var_dump(Test::$value);
}
}
function main(): void
{
Test::abc();
}
?>
--EXPECT--
int(123)
int(456)
int(456)
int(789)
int(789)
int(1000)

@ -0,0 +1,36 @@
--TEST--
Assign by reference to parent static property (parent::$value)
--FILE--
<?php
class Base
{
public static $value = 1;
}
class Child extends Base
{
public static function run()
{
$ref = &parent::$value;
var_dump(parent::$value);
var_dump(Child::$value);
$ref = 999;
var_dump(parent::$value);
var_dump(Child::$value);
var_dump(Base::$value);
}
}
function main()
{
Child::run();
}
?>
--EXPECT--
int(1)
int(1)
int(999)
int(999)
int(999)

@ -0,0 +1,25 @@
--TEST--
Reference to a typed static property preserves its type constraint
--FILE--
<?php
use native_types;
class TypedStaticReference
{
public static int $value = 1;
}
function main(): void
{
$reference = &TypedStaticReference::$value;
try {
$reference = 'invalid';
} catch (TypeError $error) {
echo "TypeError\n";
}
var_dump(TypedStaticReference::$value);
}
?>
--EXPECT--
TypeError
int(1)

@ -0,0 +1,40 @@
--TEST--
Assign by reference to static property (self / static / class name)
--FILE--
<?php
class Test
{
private static $value = 123;
public static function abc(): void
{
$a = &self::$value;
var_dump($a);
$a = 456;
var_dump(self::$value);
$b = &static::$value;
var_dump($b);
$b = 789;
var_dump(static::$value);
$c = &Test::$value;
var_dump($c);
$c = 1000;
var_dump(Test::$value);
}
}
function main(): void
{
Test::abc();
}
?>
--EXPECT--
int(123)
int(456)
int(456)
int(789)
int(789)
int(1000)
Loading…
Cancel
Save