feat(php): 添加类定义功能支持

- 实现了 StaticPropertyFetch 类型检查方法
- 扩展了 ClassDef 构造函数以支持 flags 参数
- 添加了类继承时对 final 类的检查逻辑
- 更新了示例文件 call3.php 中的类结构
- 增加了多个类相关的测试用例文件
- 修改了后置操作解析逻辑以支持静态属性访问
- 在编译器基类中添加了类声明的错误处理
- 实现了对象包装器数组初始化
- 添加了对类可见性构造函数的支持测试
- 更新了接口定义的构造函数实现
pull/1/head
韩天峰 7 months ago
parent 4ed56de30e
commit 25e567b3db
  1. 36
      examples/call3.php
  2. 5
      src/Php/AstNodeType.php
  3. 4
      src/Php/ClassDef.php
  4. 1
      src/Php/ClassLikeDef.php
  5. 28
      src/Php/CompilerBase.php
  6. 4
      src/Php/InterfaceDef.php
  7. 8
      src/Php/Translator.php
  8. 84
      tests/core/classes/class_example.phpt
  9. 23
      tests/core/classes/class_final.phpt
  10. 13
      tests/core/classes/class_stdclass.phpt
  11. 43
      tests/core/classes/clone_001.phpt
  12. 44
      tests/core/classes/clone_002.phpt
  13. 57
      tests/core/classes/clone_003.phpt
  14. 83
      tests/core/classes/clone_004.phpt
  15. 24
      tests/core/classes/clone_005.phpt
  16. 39
      tests/core/classes/clone_006.phpt
  17. 76
      tests/core/classes/ctor_visibility.phpt
  18. 35
      tests/core/classes/dereferencing_001.phpt
  19. 24
      tests/core/classes/destructor_and_echo.phpt

@ -1,21 +1,33 @@
<?php <?php
class C
class Circle
{ {
function __call($name, $values) function draw()
{ {
$values[0][0] = 'changed'; echo "Circle\n";
} }
} }
function main() {
$a = array('original');
$b = array('original');
$hack =& $b[0];
$c = new C; class Square
$c->f($a); {
$c->f($b); function draw()
{
print "Square\n";
}
}
var_dump($a, $b); function ShapeFactoryMethod($shape)
{
switch ($shape) {
case "Circle":
return new Circle();
case "Square":
return new Square();
}
} }
function main()
{
ShapeFactoryMethod("Circle")->draw();
ShapeFactoryMethod("Square")->draw();
}

@ -16,4 +16,9 @@ trait AstNodeType
{ {
return $expr instanceof Expr\PropertyFetch; return $expr instanceof Expr\PropertyFetch;
} }
protected function isStaticPropertyFetch(NodeAbstract $expr): bool
{
return $expr instanceof Expr\StaticPropertyFetch;
}
} }

@ -18,9 +18,11 @@ class ClassDef extends ClassLikeDef
public array $constants = []; public array $constants = [];
public array $implements = []; public array $implements = [];
public string $extends = ''; public string $extends = '';
public int $flags;
public function __construct(string $name, string $namespace = '') public function __construct(string $name, int $flags, string $namespace = '')
{ {
$this->flags = $flags;
parent::__construct($name, $namespace); parent::__construct($name, $namespace);
} }
} }

@ -8,6 +8,7 @@ class ClassLikeDef
public string $namespace; public string $namespace;
public string $extends = ''; public string $extends = '';
public function __construct(string $name, string $namespace = '') public function __construct(string $name, string $namespace = '')
{ {
$this->name = $name; $this->name = $name;

@ -210,6 +210,7 @@ class CompilerBase extends \PhpAot\Core\Translator
{ {
$this->localVars = []; $this->localVars = [];
$this->arguments = []; $this->arguments = [];
$this->objectWrappers = [];
$this->tmpVarIndex = 0; $this->tmpVarIndex = 0;
$this->inLoop = false; $this->inLoop = false;
} }
@ -495,6 +496,9 @@ class CompilerBase extends \PhpAot\Core\Translator
case 'Stmt_TryCatch': case 'Stmt_TryCatch':
$result = $this->parseTryCatch($v); $result = $this->parseTryCatch($v);
break; break;
case 'Stmt_Class':
$this->fatalError($v, 'Cannot declare class in function');
break;
default: default:
abort($v); abort($v);
} }
@ -1437,21 +1441,38 @@ class CompilerBase extends \PhpAot\Core\Translator
return $this->parseIdentifier($arg->value); return $this->parseIdentifier($arg->value);
} }
protected function parsePostInc($expr): string protected function parsePostOp($expr, string $op): string
{ {
if ($this->isVarExpr($expr->var)) { if ($this->isVarExpr($expr->var)) {
return $this->parseIdentifier($expr->var) . '++'; return $this->parseIdentifier($expr->var) . '++';
} elseif ($this->isPropertyFetch($expr->var)) { } elseif ($this->isPropertyFetch($expr->var)) {
$obj = $this->parseIdentifier($expr->var->var); $obj = $this->parseIdentifier($expr->var->var);
$prop = $this->identifierToStr($expr->var->name); $prop = $this->identifierToStr($expr->var->name);
return $obj . '.setProperty(' . $prop . ', ' . $obj. '.getProperty(' . $prop . ') + 1)'; $tmpVar = $this->genTmpVarName();
$this->addLocalVar($tmpVar, self::TYPE_VAR);
$this->beforeStmtLines[] = $tmpVar . ' = ' . $obj. '.getProperty(' . $prop . ');';
$this->afterStmtLines[] = $obj . '.setProperty(' . $prop . ', ' . $tmpVar . ' ' . $op . ' 1);';
return $tmpVar;
} elseif ($this->isStaticPropertyFetch($expr->var)) {
$class = $this->identifierToStr($expr->var->class);
$prop = $this->identifierToStr($expr->var->name);
$tmpVar = $this->genTmpVarName();
$this->addLocalVar($tmpVar, self::TYPE_VAR);
$this->beforeStmtLines[] = $tmpVar . ' = ' . 'php::getStaticProperty(' . $class . ', ' . $prop . ');';
$this->afterStmtLines[] = 'php::setStaticProperty(' . $class . ', ' . $prop . ', ' . $tmpVar . ' ' . $op . ' 1);';
return $tmpVar;
} }
abort($expr, "Post-increment operator is not supported for non-variable expressions"); abort($expr, "Post-increment operator is not supported for non-variable expressions");
} }
protected function parsePostDec($expr): string protected function parsePostDec($expr): string
{ {
return $this->parseIdentifier($expr->var) . '--'; return $this->parsePostOp($expr, '-');
}
protected function parsePostInc($expr): string
{
return $this->parsePostOp($expr, '+');
} }
protected function parseTernary(mixed $expr): string protected function parseTernary(mixed $expr): string
@ -2254,6 +2275,7 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function convertToObject(Node $object): string protected function convertToObject(Node $object): string
{ {
$id = $this->parseIdentifier($object); $id = $this->parseIdentifier($object);
// TODO 这里的逻辑是不是错误
if ($this->isVarExpr($object) and !$this->hasVar($id)) { if ($this->isVarExpr($object) and !$this->hasVar($id)) {
$this->addLocalVar($id, self::TYPE_OBJECT); $this->addLocalVar($id, self::TYPE_OBJECT);
return $id; return $id;

@ -4,4 +4,8 @@ namespace PhpAot\Php;
class InterfaceDef extends ClassLikeDef class InterfaceDef extends ClassLikeDef
{ {
public function __construct(string $name, string $namespace = '')
{
parent::__construct($name, $namespace);
}
} }

@ -512,9 +512,15 @@ class Translator extends Preprocessor
$this->genClassStubFile($class, $this->file); $this->genClassStubFile($class, $this->file);
} }
$this->classDef = new ClassDef($this->class, $this->namespace); $this->classDef = new ClassDef($this->class, $class->flags, $this->namespace);
if ($class->extends) { if ($class->extends) {
$this->classDef->extends = $this->parseIdentifier($class->extends); $this->classDef->extends = $this->parseIdentifier($class->extends);
if (isset($this->classes[$this->classDef->extends])) {
$parent = $this->classes[$this->classDef->extends];
if ($parent->flags & Modifiers::FINAL) {
$this->fatalError($class, "Class `{$this->class}` cannot extend final class `{$this->classDef->extends}`");
}
}
} }
$this->classDef->implements = $this->parseIdentifierList($class->implements); $this->classDef->implements = $this->parseIdentifierList($class->implements);

@ -0,0 +1,84 @@
--TEST--
Classes general test
--FILE--
<?php
/* pretty nifty object-oriented code! */
class user {
public $first_name,$family_name,$address,$phone_num;
function display()
{
echo "User information\n";
echo "----------------\n\n";
echo "First name:\t ".$this->first_name."\n";
echo "Family name:\t ".$this->family_name."\n";
echo "Address:\t ".$this->address."\n";
echo "Phone:\t\t ".$this->phone_num."\n";
echo "\n\n";
}
function initialize($first_name,$family_name,$address,$phone_num)
{
$this->first_name = $first_name;
$this->family_name = $family_name;
$this->address = $address;
$this->phone_num = $phone_num;
}
};
function test($u)
{ /* one can pass classes as arguments */
$u->display();
$t = $u;
$t->address = "New address...";
return $t; /* and also return them as return values */
}
function main() {
$user1 = new user;
$user2 = new user;
$user1->initialize("Zeev","Suraski","Ben Gourion 3, Kiryat Bialik, Israel","+972-4-8713139");
$user2->initialize("Andi","Gutmans","Haifa, Israel","+972-4-8231621");
$user1->display();
$user2->display();
$tmp = test($user2);
$tmp->display();
}
?>
--EXPECT--
User information
----------------
First name: Zeev
Family name: Suraski
Address: Ben Gourion 3, Kiryat Bialik, Israel
Phone: +972-4-8713139
User information
----------------
First name: Andi
Family name: Gutmans
Address: Haifa, Israel
Phone: +972-4-8231621
User information
----------------
First name: Andi
Family name: Gutmans
Address: Haifa, Israel
Phone: +972-4-8231621
User information
----------------
First name: Andi
Family name: Gutmans
Address: New address...
Phone: +972-4-8231621

@ -0,0 +1,23 @@
--TEST--
ZE2 A final class cannot be inherited
--SKIPIF--
<?php die('skip'); ?>
--FILE--
<?php
final class base {
function show() {
echo "base\n";
}
}
class derived extends base {
}
function main() {
$t = new base();
echo "Done\n"; // shouldn't be displayed
}
?>
--EXPECTF--
Fatal error: Class derived cannot extend final class base in %s on line %d

@ -0,0 +1,13 @@
--TEST--
Instantiate stdClass
--FILE--
<?php
function main() {
$obj = new stdClass;
echo get_class($obj)."\n";
echo "Done\n";
}
?>
--EXPECT--
stdClass
Done

@ -0,0 +1,43 @@
--TEST--
ZE2 object cloning, 1
--FILE--
<?php
class test {
public $p1 = 1;
public $p2 = 2;
public $p3;
};
function main() {
$obj = new test;
$obj->p2 = 'A';
$obj->p3 = 'B';
$copy = clone $obj;
$copy->p3 = 'C';
echo "Object\n";
var_dump($obj);
echo "Clown\n";
var_dump($copy);
echo "Done\n";
}
?>
--EXPECT--
Object
object(test)#1 (3) {
["p1"]=>
int(1)
["p2"]=>
string(1) "A"
["p3"]=>
string(1) "B"
}
Clown
object(test)#2 (3) {
["p1"]=>
int(1)
["p2"]=>
string(1) "A"
["p3"]=>
string(1) "C"
}
Done

@ -0,0 +1,44 @@
--TEST--
ZE2 object cloning, 2
--FILE--
<?php
class test {
public $p1 = 1;
public $p2 = 2;
public $p3;
public function __clone() {
}
};
function main() {
$obj = new test;
$obj->p2 = 'A';
$obj->p3 = 'B';
$copy = clone $obj;
$copy->p3 = 'C';
echo "Object\n";
var_dump($obj);
echo "Clown\n";
var_dump($copy);
echo "Done\n";
}
?>
--EXPECT--
Object
object(test)#1 (3) {
["p1"]=>
int(1)
["p2"]=>
string(1) "A"
["p3"]=>
string(1) "B"
}
Clown
object(test)#2 (3) {
["p1"]=>
int(1)
["p2"]=>
string(1) "A"
["p3"]=>
string(1) "C"
}
Done

@ -0,0 +1,57 @@
--TEST--
ZE2 object cloning, 3
--FILE--
<?php
class base {
protected $p1 = 'base:1';
public $p2 = 'base:2';
public $p3 = 'base:3';
public $p4 = 'base:4';
public $p5 = 'base:5';
private $p6 = 'base:6';
public function __clone() {
}
};
class test extends base {
public $p1 = 'test:1';
public $p3 = 'test:3';
public $p4 = 'test:4';
public $p5 = 'test:5';
public function __clone() {
$this->p5 = 'clone:5';
}
}
function main() {
$obj = new test;
$obj->p4 = 'A';
$copy = clone $obj;
echo "Object\n";
print_r($obj);
echo "Clown\n";
print_r($copy);
echo "Done\n";
}
?>
--EXPECT--
Object
test Object
(
[p1] => test:1
[p2] => base:2
[p3] => test:3
[p4] => A
[p5] => test:5
[p6:base:private] => base:6
)
Clown
test Object
(
[p1] => test:1
[p2] => base:2
[p3] => test:3
[p4] => A
[p5] => clone:5
[p6:base:private] => base:6
)
Done

@ -0,0 +1,83 @@
--TEST--
ZE2 object cloning, 4
--FILE--
<?php
abstract class base {
public $a = 'base';
// disallow cloning
private function __clone() {}
}
class test extends base {
public $b = 'test';
// re-enable cloning
public function __clone() {}
public function show() {
var_dump($this);
}
}
function main() {
echo "Original\n";
$o1 = new test;
$o1->a = array(1,2);
$o1->b = array(3,4);
$o1->show();
echo "Clone\n";
$o2 = clone $o1;
$o2->show();
echo "Modify\n";
$o2->a = 5;
$o2->b = 6;
$o2->show();
echo "Done\n";
}
?>
--EXPECT--
Original
object(test)#1 (2) {
["a"]=>
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
["b"]=>
array(2) {
[0]=>
int(3)
[1]=>
int(4)
}
}
Clone
object(test)#2 (2) {
["a"]=>
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
["b"]=>
array(2) {
[0]=>
int(3)
[1]=>
int(4)
}
}
Modify
object(test)#2 (2) {
["a"]=>
int(5)
["b"]=>
int(6)
}
Done

@ -0,0 +1,24 @@
--TEST--
ZE2 object cloning, 5
--SKIPIF--
<?php die('skip, failed at compile time'); ?>
--FILE--
<?php
abstract class base {
public $a = 'base';
// disallow cloning once forever
final protected function __clone() {}
}
class test extends base {
// reenabling should fail
public function __clone() {}
}
function main() {
}
?>
--EXPECTF--
Fatal error: Cannot override final method base::__clone() in %sclone_005.php on line 11

@ -0,0 +1,39 @@
--TEST--
ZE2 object cloning, 6
--INI--
error_reporting=2047
--FILE--
<?php
class MyCloneable {
static $id = 0;
function __construct() {
$this->id = self::$id++;
}
function __clone() {
$this->address = "New York";
$this->id = self::$id++;
}
}
function main() {
$original = new MyCloneable();
$original->name = "Hello";
$original->address = "Tel-Aviv";
echo $original->id . "\n";
$clone = clone $original;
echo $clone->id . "\n";
echo $clone->name . "\n";
echo $clone->address . "\n";
}
?>
--EXPECTF--
0
1
Hello
New York

@ -0,0 +1,76 @@
--TEST--
ZE2 A private constructor cannot be called
--SKIPIF--
<?php die('skip'); ?>
--FILE--
<?php
class Test
{
function __construct()
{
echo __METHOD__ . "()\n";
}
}
class Derived extends Test
{
function __construct()
{
echo __METHOD__ . "()\n";
parent::__construct();
}
static function f()
{
new Derived;
}
}
class TestPriv
{
private function __construct()
{
echo __METHOD__ . "()\n";
}
static function f()
{
new TestPriv;
}
}
class DerivedPriv extends TestPriv
{
function __construct()
{
echo __METHOD__ . "()\n";
parent::__construct();
}
static function f()
{
new DerivedPriv;
}
}
function main() {
Derived::f();
TestPriv::f();
DerivedPriv::f();
}
?>
--EXPECTF--
Derived::__construct()
Test::__construct()
TestPriv::__construct()
DerivedPriv::__construct()
Fatal error: Uncaught Error: Cannot call private TestPriv::__construct() in %sctor_visibility.php:%d
Stack trace:
#0 %s(%d): DerivedPriv->__construct()
#1 %s(%d): DerivedPriv::f()
#2 {main}
thrown in %sctor_visibility.php on line %d

@ -0,0 +1,35 @@
--TEST--
ZE2 dereferencing of objects from methods
--SKIPIF--
<?php die('skip'); ?>
--FILE--
<?php
class Name {
function __construct(public string $name) {}
function display() {
echo $this->name . "\n";
}
}
class Person {
private $name;
function __construct($_name, $_address) {
$this->name = new Name($_name);
}
function getName() {
return $this->name;
}
}
function main() {
$person = new Person("John", "New York");
$person->getName()->display();
}
?>
--EXPECT--
John

@ -0,0 +1,24 @@
--TEST--
ZE2 Destructors and echo
--FILE--
<?php
class Test
{
function __construct() {
echo __METHOD__ . "\n";
}
function __destruct() {
echo __METHOD__ . "\n";
}
}
function main() {
$o = new Test;
}
?>
--EXPECT--
Test::__construct
Test::__destruct
Loading…
Cancel
Save