feat(compiler): 添加类常量支持和优化代码生成

- 引入 NodeAbstract 类型用于对象转换
- 移除 Stmt_Nop 语句的空注释生成
- 为 Foreach 循环体添加前置语句解析
- 简化对象类型检查逻辑
- 添加多个类常量测试用例
- 在预处理器和翻译器中处理 Stmt_Nop 语句类型
- 完善类常量声明、继承和访问功能
pull/1/head
韩天峰 7 months ago
parent 25e567b3db
commit d90bf8a5fd
  1. 20
      src/Php/CompilerBase.php
  2. 1
      src/Php/Preprocessor.php
  3. 2
      src/Php/Translator.php
  4. 88
      tests/core/classes/constants_basic_001.phpt
  5. 33
      tests/core/classes/constants_basic_002.phpt
  6. 5
      tests/core/classes/constants_basic_003.inc
  7. 33
      tests/core/classes/constants_basic_003.phpt
  8. 102
      tests/core/classes/constants_basic_004.phpt
  9. 19
      tests/core/classes/constants_basic_005.phpt
  10. 44
      tests/core/classes/constants_basic_006.phpt
  11. 37
      tests/core/classes/constants_comments_001.phpt

@ -12,6 +12,7 @@ use PhpParser\Error;
use PhpParser\Node\NullableType;
use PhpParser\Node\Scalar\MagicConst;
use PhpParser\Node\Stmt\Foreach_;
use PhpParser\NodeAbstract;
use PhpParser\NodeFinder;
use PhpParser\NodeTraverser;
use PhpParser\Parser;
@ -482,7 +483,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$result = $this->parseContinue($v);
break;
case 'Stmt_Nop':
$result = '// pass';
$result = '';
break;
case 'Stmt_Global':
$result = $this->parseGlobal($v);
@ -2027,9 +2028,13 @@ class CompilerBase extends \PhpAot\Core\Translator
$valueVar = $this->parseIdentifier($node->valueVar);
$code .= self::TYPE_VAR . ' ' . $this->getIndent() . ' ' . $valueVar . ' = iter.value();' . PHP_EOL;
}
$code .= $this->parseStmts($node->stmts);
$body = $this->parseStmts($node->stmts);
$this->indentLevel--;
$code .= $this->parseBeforeStmtLines() . PHP_EOL;
$code .= $body . PHP_EOL;
$code .= $this->getIndent() . '}';
return $code;
@ -2272,17 +2277,10 @@ class CompilerBase extends \PhpAot\Core\Translator
return $expr;
}
protected function convertToObject(Node $object): string
protected function convertToObject(NodeAbstract $object): string
{
$id = $this->parseIdentifier($object);
// TODO 这里的逻辑是不是错误
if ($this->isVarExpr($object) and !$this->hasVar($id)) {
$this->addLocalVar($id, self::TYPE_OBJECT);
return $id;
}
$type = $this->getVarType($id);
if ($type === self::TYPE_OBJECT) {
if ($this->isVarExpr($object) and $this->getVarType($id) === self::TYPE_OBJECT) {
return $id;
}

@ -149,6 +149,7 @@ class Preprocessor extends CompilerBase
switch ($type) {
case 'Stmt_ClassConst':
case 'Stmt_Property':
case 'Stmt_Nop':
break;
case 'Stmt_ClassMethod':
$code .= $this->prepareFunction($v) . PHP_EOL;

@ -542,6 +542,8 @@ class Translator extends Preprocessor
case 'Stmt_ClassMethod':
$this->parseClassMethod($v, $methodCodes);
break;
case 'Stmt_Nop':
break;
default:
abort($v);
}

@ -0,0 +1,88 @@
--TEST--
Class constant declarations
--SKIPIF--
<?php die('skip, failed at compile time'); ?>
--FILE--
<?php
class C
{
const c1 = 1, c2 = 1.5;
const c3 = + 1, c4 = + 1.5;
const c5 = -1, c6 = -1.5;
const c7 = __LINE__;
const c8 = __FILE__;
const c9 = __CLASS__;
const c10 = __METHOD__;
const c11 = __FUNCTION__;
const c12 = DEFINED;
const c13 = DEFINED_TO_VAR;
const c14 = DEFINED_TO_UNDEF_VAR;
const c15 = "hello1";
const c16 = 'hello2';
const c17 = C::c16;
const c18 = self::c17;
}
function main() {
define('DEFINED', 1234);
$def = 456;
define('DEFINED_TO_VAR', $def);
define('DEFINED_TO_UNDEF_VAR', $undef);
echo "\nAttempt to access various kinds of class constants:\n";
var_dump(C::c1);
var_dump(C::c2);
var_dump(C::c3);
var_dump(C::c4);
var_dump(C::c5);
var_dump(C::c6);
var_dump(C::c7);
var_dump(C::c8);
var_dump(C::c9);
var_dump(C::c10);
var_dump(C::c11);
var_dump(C::c12);
var_dump(C::c13);
var_dump(C::c14);
var_dump(C::c15);
var_dump(C::c16);
var_dump(C::c17);
var_dump(C::c18);
echo "\nExpecting fatal error:\n";
var_dump(C::c19);
echo "\nYou should not see this.";
}
?>
--EXPECTF--
Warning: Undefined variable $undef in %s on line %d
Attempt to access various kinds of class constants:
int(1)
float(1.5)
int(1)
float(1.5)
int(-1)
float(-1.5)
int(13)
string(%d) "%s"
string(1) "C"
string(0) ""
string(0) ""
int(1234)
int(456)
NULL
string(6) "hello1"
string(6) "hello2"
string(6) "hello2"
string(6) "hello2"
Expecting fatal error:
Fatal error: Uncaught Error: Undefined constant C::c19 in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d

@ -0,0 +1,33 @@
--TEST--
Basic class support - defining and reading a class constant.
--FILE--
<?php
class aclass
{
const myConst = "hello";
}
function main() {
echo "\nRead class constant.\n";
var_dump(aclass::myConst);
echo "\nFail to read class constant from instance.\n";
$myInstance = new aclass();
var_dump($myInstance->myConst);
echo "\nClass constant not visible in object var_dump.\n";
var_dump($myInstance);
}
?>
--EXPECTF--
Read class constant.
string(5) "hello"
Fail to read class constant from instance.
Warning: Undefined property: aclass::$myConst in %s on line %d
NULL
Class constant not visible in object var_dump.
object(aclass)#%d (0) {
}

@ -0,0 +1,5 @@
<?php
class A {
const MY_CONST = "hello from A";
}
?>

@ -0,0 +1,33 @@
--TEST--
Ensure class properties and constants can be defined in terms of constants that are not known at compile time.
--SKIPIF--
<?php die('skip, failed at compile time'); ?>
--FILE--
<?php
class B
{
public static $a = A::MY_CONST;
public static $c = C::MY_CONST;
const ca = A::MY_CONST;
const cc = C::MY_CONST;
}
class C
{
const MY_CONST = "hello from C";
}
function main() {
include __DIR__.'/constants_basic_003.inc';
var_dump(B::$a);
var_dump(B::$c);
var_dump(B::ca);
var_dump(B::cc);
}
?>
--EXPECT--
string(12) "hello from A"
string(12) "hello from C"
string(12) "hello from A"
string(12) "hello from C"

@ -0,0 +1,102 @@
--TEST--
Test properties with array default values using class constants as keys and values.
--SKIPIF--
<?php die('skip, failed at compile time'); ?>
--FILE--
<?php
class X
{
// Static and instance array using class constants
public static $sa_x = array(B::KEY => B::VALUE);
public $a_x = array(B::KEY => B::VALUE);
}
class B
{
const KEY = "key";
const VALUE = "value";
// Static and instance array using class constants with self
public static $sa_b = array(self::KEY => self::VALUE);
public $a_b = array(self::KEY => self::VALUE);
}
class C extends B
{
// Static and instance array using class constants with parent
public static $sa_c_parent = array(parent::KEY => parent::VALUE);
public $a_c_parent = array(parent::KEY => parent::VALUE);
// Static and instance array using class constants with self (constants should be inherited)
public static $sa_c_self = array(self::KEY => self::VALUE);
public $a_c_self = array(self::KEY => self::VALUE);
// Should also include inherited properties from B.
}
function main() {
echo "\nStatic properties:\n";
var_dump(X::$sa_x, B::$sa_b, C::$sa_b, C::$sa_c_parent, C::$sa_c_self);
echo "\nInstance properties:\n";
$x = new x;
$b = new B;
$c = new C;
var_dump($x, $b, $c);
}
?>
--EXPECTF--
Static properties:
array(1) {
["key"]=>
string(5) "value"
}
array(1) {
["key"]=>
string(5) "value"
}
array(1) {
["key"]=>
string(5) "value"
}
array(1) {
["key"]=>
string(5) "value"
}
array(1) {
["key"]=>
string(5) "value"
}
Instance properties:
object(X)#%d (1) {
["a_x"]=>
array(1) {
["key"]=>
string(5) "value"
}
}
object(B)#%d (1) {
["a_b"]=>
array(1) {
["key"]=>
string(5) "value"
}
}
object(C)#%d (3) {
["a_b"]=>
array(1) {
["key"]=>
string(5) "value"
}
["a_c_parent"]=>
array(1) {
["key"]=>
string(5) "value"
}
["a_c_self"]=>
array(1) {
["key"]=>
string(5) "value"
}
}

@ -0,0 +1,19 @@
--TEST--
Test constants with default values based on other constants.
--SKIPIF--
<?php die('skip, failed at compile time'); ?>
--FILE--
<?php
class C
{
const CONST_2 = self::CONST_1;
const CONST_1 = self::BASE_CONST;
const BASE_CONST = 'hello';
}
function main() {
var_dump(C::CONST_1, C::CONST_2);
}
?>
--EXPECT--
string(5) "hello"
string(5) "hello"

@ -0,0 +1,44 @@
--TEST--
Ensure class constants are not evaluated when a class is looked up to resolve inheritance during runtime.
--SKIPIF--
<?php die('skip, failed at compile time'); ?>
--FILE--
<?php
const K = "nasty";
class C
{
const X = E::A;
public static $a = array(K => D::V, E::A => K);
}
class E extends D
{
const A = "hello";
}
function main() {
eval('class D extends C { const V = \'test\'; }');
var_dump(C::X, C::$a, D::X, D::$a, E::X, E::$a);
}
?>
--EXPECT--
string(5) "hello"
array(2) {
["nasty"]=>
string(4) "test"
["hello"]=>
string(5) "nasty"
}
string(5) "hello"
array(2) {
["nasty"]=>
string(4) "test"
["hello"]=>
string(5) "nasty"
}
string(5) "hello"
array(2) {
["nasty"]=>
string(4) "test"
["hello"]=>
string(5) "nasty"
}

@ -0,0 +1,37 @@
--TEST--
Class constants and doc comments
--INI--
opcache.save_comments=1
--FILE--
<?php
class X {
/** comment X1 */
const X1 = 1;
const X2 = 2;
/** comment X3 */
const X3 = 3;
}
class Y extends X {
/** comment Y1 */
const Y1 = 1;
const Y2 = 2;
/** comment Y3 */
const Y3 = 3;
}
function main() {
$r = new ReflectionClass('Y');
foreach ($r->getReflectionConstants() as $rc) {
echo $rc->getName() . " : " . $rc->getValue() . "\n";
}
}
?>
--EXPECT--
X1 : 1
X2 : 2
X3 : 3
Y1 : 1
Y2 : 2
Y3 : 3
Loading…
Cancel
Save