N°4789 - Parse datamodel module.xxx.php files instead of interpreting them (#746)

* N°4789 - Parse datamodel module.xxx.php files instead of interpreting them - refactoring all in a dedicated service first

* N°4789 - fix broken setup + tests

* N°4789 - replace legacy eval by module file parsing

* N°4789 - handle constants and if conditional structures

* N°4789 - compute boolean expressions

* N°4789 - make autoselect and dependencies work as well

* cleanup

* N°4789 - fix BeforeWritingConfig calls during setup

* N°4789 - refactor and split in ModuleDiscoveryEvaluationService + handle ModuleInstallerAPI methods calls during setup

* N°4789 - PR review changes with Romain

* PR review + code cleanup + added usecases and test cover

* temp evaluation work

* replace eval by iTop custom evaluation classes

* move PhpParser/Evaluation classes in a specific namespave + composer dumpautoload

* fix broken setup

* fix broken setup

* complete Evaluators list + autoload

* cleanup useless testing resources

* cleanup + replace last eval call in VariableEvaluator

* fix few Evaluators code

* enhance nikic evaluators + test with/without nikic lib

* Evaluator fixes/enhancements + tests

* bump to nikic fork temporarly

* bump nikic-parser fork + use only nikic fork  evaluation + cleanup itop redondant evaluators

* review with Romain: use distinct whitelists in setup time/runtime + move ModuleFileParser internal logic into ModuleFileReader

* PhpExpressionEvaluator used via constructor and not as a service

* dumpautoload again after rebase
This commit is contained in:
odain-cbd
2025-09-09 17:54:18 +02:00
committed by GitHub
parent 2ee68ff819
commit 15103dc49f
51 changed files with 3199 additions and 1334 deletions

View File

@@ -24,7 +24,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "5.0-dev"
"dev-master": "5.x-dev"
}
},
"autoload": {

View File

@@ -3,7 +3,10 @@
namespace PhpParser;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar;
use Exception;
use function array_merge;
@@ -30,6 +33,12 @@ class ConstExprEvaluator {
/** @var callable|null */
private $fallbackEvaluator;
/** @var array $functionsWhiteList */
private $functionsWhiteList;
/** @var array $staticCallsWhitelist */
private $staticCallsWhitelist;
/**
* Create a constant expression evaluator.
*
@@ -44,6 +53,17 @@ class ConstExprEvaluator {
"Expression of type {$expr->getType()} cannot be evaluated"
);
};
$this->functionsWhiteList = [];
$this->staticCallsWhitelist = [];
}
public function setFunctionsWhitelist(array $functionsWhiteList): void {
$this->functionsWhiteList = $functionsWhiteList;
}
public function setStaticCallsWhitelist(array $staticCallsWhitelist): void {
$this->staticCallsWhitelist = $staticCallsWhitelist;
}
/**
@@ -115,6 +135,10 @@ class ConstExprEvaluator {
return $this->evaluateArray($expr);
}
if ($expr instanceof Expr\Variable) {
return $this->evaluateVariable($expr);
}
// Unary operators
if ($expr instanceof Expr\UnaryPlus) {
return +$this->evaluate($expr->expr);
@@ -145,6 +169,38 @@ class ConstExprEvaluator {
return $this->evaluateConstFetch($expr);
}
if ($expr instanceof Expr\Isset_) {
return $this->evaluateIsset($expr);
}
if ($expr instanceof Expr\ClassConstFetch) {
return $this->evaluateClassConstFetch($expr);
}
if ($expr instanceof Expr\Cast) {
return $this->evaluateCast($expr);
}
if ($expr instanceof Expr\StaticPropertyFetch) {
return $this->evaluateStaticPropertyFetch($expr);
}
if ($expr instanceof Expr\FuncCall) {
return $this->evaluateFuncCall($expr);
}
if ($expr instanceof Expr\StaticCall) {
return $this->evaluateStaticCall($expr);
}
if ($expr instanceof Expr\NullsafePropertyFetch||$expr instanceof Expr\PropertyFetch) {
return $this->evaluatePropertyFetch($expr);
}
if ($expr instanceof Expr\NullsafeMethodCall||$expr instanceof Expr\MethodCall) {
return $this->evaluateMethodCall($expr);
}
return ($this->fallbackEvaluator)($expr);
}
@@ -175,12 +231,15 @@ class ConstExprEvaluator {
/** @return mixed */
private function evaluateBinaryOp(Expr\BinaryOp $expr) {
if ($expr instanceof Expr\BinaryOp\Coalesce
&& $expr->left instanceof Expr\ArrayDimFetch
) {
// This needs to be special cased to respect BP_VAR_IS fetch semantics
return $this->evaluate($expr->left->var)[$this->evaluate($expr->left->dim)]
?? $this->evaluate($expr->right);
if ($expr instanceof Expr\BinaryOp\Coalesce) {
try {
$var = $this->evaluate($expr->left);
} catch(\Throwable $t) {
//left expression cannot be evaluated (! isset for exeample)
return $this->evaluate($expr->right);
}
return $var ?? $this->evaluate($expr->right);
}
// The evaluate() calls are repeated in each branch, because some of the operators are
@@ -225,13 +284,272 @@ class ConstExprEvaluator {
/** @return mixed */
private function evaluateConstFetch(Expr\ConstFetch $expr) {
$name = $expr->name->toLowerString();
switch ($name) {
case 'null': return null;
case 'false': return false;
case 'true': return true;
}
try {
$name = $expr->name;
if(! is_string($name)) {
//PHP_VERSION_ID usecase
$name = $name->name;
}
if (defined($name)) {
return constant($name);
}
} catch(\Throwable $t) {}
return ($this->fallbackEvaluator)($expr);
}
/** @return bool */
private function evaluateIsset(Expr\Isset_ $expr) {
try {
foreach ($expr->vars as $var) {
$var = $this->evaluate($var);
if (! isset($var)) {
return false;
}
}
return true;
} catch(\Throwable $t) {
return false;
}
}
/** @return mixed */
private function evaluateClassConstFetch(Expr\ClassConstFetch $expr) {
try {
$classname = $expr->class->name;
$property = $expr->name->name;
if ('class' === $property) {
return $classname;
}
if (class_exists($classname)) {
$class = new \ReflectionClass($classname);
if (array_key_exists($property, $class->getConstants())) {
$oReflectionConstant = $class->getReflectionConstant($property);
if ($oReflectionConstant->isPublic()) {
return $class->getConstant($property);
}
}
}
} catch(\Throwable $t) {}
return ($this->fallbackEvaluator)($expr);
}
/** @return mixed */
private function evaluateCast(Expr\Cast $expr) {
try {
$subexpr = $this->evaluate($expr->expr);
$type = get_class($expr);
switch ($type) {
case Expr\Cast\Array_::class:
return (array) $subexpr;
case Expr\Cast\Bool_::class:
return (bool) $subexpr;
case Expr\Cast\Double::class:
switch ($expr->getAttribute("kind")) {
case Expr\Cast\Double::KIND_DOUBLE:
return (double) $subexpr;
case Expr\Cast\Double::KIND_FLOAT:
case Expr\Cast\Double::KIND_REAL:
return (float) $subexpr;
}
break;
case Expr\Cast\Int_::class:
return (int) $subexpr;
case Expr\Cast\Object_::class:
return (object) $subexpr;
case Expr\Cast\String_::class:
return (string) $subexpr;
}
} catch(\Throwable $t) {}
return ($this->fallbackEvaluator)($expr);
}
/** @return mixed */
private function evaluateStaticPropertyFetch(Expr\StaticPropertyFetch $expr) {
try {
$classname = $expr->class->name;
if ($expr->name instanceof Identifier) {
$property = $expr->name->name;
} else {
$property = $this->evaluate($expr->name);
}
if (class_exists($classname)) {
$class = new \ReflectionClass($classname);
if (array_key_exists($property, $class->getStaticProperties())) {
$oReflectionProperty = $class->getProperty($property);
if ($oReflectionProperty->isPublic()) {
return $class->getStaticPropertyValue($property);
}
}
}
}
catch (\Throwable $t) {}
return ($this->fallbackEvaluator)($expr);
}
/** @return mixed */
private function evaluateFuncCall(Expr\FuncCall $expr) {
try {
$name = $expr->name;
if ($name instanceof Name) {
$function = $name->name;
} else {
$function = $this->evaluate($name);
}
if (! in_array($function, $this->functionsWhiteList)) {
throw new Exception("FuncCall $function not supported");
}
$args=[];
foreach ($expr->args as $arg) {
/** @var \PhpParser\Node\Arg $arg */
$args[]=$arg->value->value;
}
$reflection_function = new \ReflectionFunction($function);
return $reflection_function->invoke(...$args);
}
catch (\Throwable $t) {}
return ($this->fallbackEvaluator)($expr);
}
/** @return mixed */
private function evaluateVariable(Expr\Variable $expr) {
try {
$name = $expr->name;
if (array_key_exists($name, get_defined_vars())) {
return $$name;
}
if (array_key_exists($name, $GLOBALS)) {
global $$name;
return $$name;
}
} catch (\Throwable $t) {
}
return ($this->fallbackEvaluator)($expr);
}
/** @return mixed */
private function evaluateStaticCall(Expr\StaticCall $expr) {
try {
$class = $expr->class->name;
if ($expr->name instanceof Identifier) {
$method = $expr->name->name;
} else {
$method = $this->evaluate($expr->name);
}
$static_call_description = "$class::$method";
if (! in_array($static_call_description, $this->staticCallsWhitelist)) {
throw new Exception("StaticCall $static_call_description not supported");
}
$args=[];
foreach ($expr->args as $arg) {
/** @var \PhpParser\Node\Arg $arg */
$args[]=$arg->value->value;
}
$class = new \ReflectionClass($class);
$method = $class->getMethod($method);
if ($method->isPublic()) {
return $method->invokeArgs(null, $args);
}
} catch (\Throwable $t) {}
return ($this->fallbackEvaluator)($expr);
}
/**
* @param \PhpParser\Node\Expr\NullsafePropertyFetch|\PhpParser\Node\Expr\PropertyFetch $expr
*
* @return mixed
*/
private function evaluatePropertyFetch($expr) {
try {
$var = $this->evaluate($expr->var);
} catch (\Throwable $t) {
$var = null;
}
if (! is_null($var)) {
try {
if ($expr->name instanceof Identifier) {
$name = $expr->name->name;
} else {
$name = $this->evaluate($expr->name);
}
$reflectionClass = new \ReflectionClass(get_class($var));
$property = $reflectionClass->getProperty($name);
if ($property->isPublic()) {
return $property->getValue($var);
}
}
catch (\Throwable $t) {}
} else if ($expr instanceof Expr\NullsafePropertyFetch) {
return null;
}
return ($this->fallbackEvaluator)($expr);
}
/**
* @param \PhpParser\Node\Expr\MethodCall|\PhpParser\Node\Expr\NullsafeMethodCall $expr
*
* @return mixed
*/
private function evaluateMethodCall($expr) {
try {
$var = $this->evaluate($expr->var);
} catch (\Throwable $t) {
$var = null;
}
if (! is_null($var)) {
try {
$args = [];
foreach ($expr->args as $arg) {
/** @var \PhpParser\Node\Arg $arg */
$args[] = $arg->value->value;
}
if ($expr->name instanceof Identifier) {
$name = $expr->name->name;
} else {
$name = $this->evaluate($expr->name);
}
$reflectionClass = new \ReflectionClass(get_class($var));
$method = $reflectionClass->getMethod($name);
if ($method->isPublic()) {
return $method->invokeArgs($var, $args);
}
}
catch (\Throwable $t) {}
} else if ($expr instanceof Expr\NullsafeMethodCall) {
return null;
}
return ($this->fallbackEvaluator)($expr);
}
}

View File

@@ -5,7 +5,11 @@ namespace PhpParser\Node\Expr;
require __DIR__ . '/../ArrayItem.php';
if (false) {
// For classmap-authoritative support.
/**
* For classmap-authoritative support.
*
* @deprecated use \PhpParser\Node\ArrayItem instead.
*/
class ArrayItem extends \PhpParser\Node\ArrayItem {
}
}

View File

@@ -5,6 +5,10 @@ namespace PhpParser\Node\Expr\Cast;
use PhpParser\Node\Expr\Cast;
class Bool_ extends Cast {
// For use in "kind" attribute
public const KIND_BOOL = 1; // "bool" syntax
public const KIND_BOOLEAN = 2; // "boolean" syntax
public function getType(): string {
return 'Expr_Cast_Bool';
}

View File

@@ -5,6 +5,10 @@ namespace PhpParser\Node\Expr\Cast;
use PhpParser\Node\Expr\Cast;
class Int_ extends Cast {
// For use in "kind" attribute
public const KIND_INT = 1; // "int" syntax
public const KIND_INTEGER = 2; // "integer" syntax
public function getType(): string {
return 'Expr_Cast_Int';
}

View File

@@ -5,6 +5,10 @@ namespace PhpParser\Node\Expr\Cast;
use PhpParser\Node\Expr\Cast;
class String_ extends Cast {
// For use in "kind" attribute
public const KIND_STRING = 1; // "string" syntax
public const KIND_BINARY = 2; // "binary" syntax
public function getType(): string {
return 'Expr_Cast_String';
}

View File

@@ -5,7 +5,11 @@ namespace PhpParser\Node\Expr;
require __DIR__ . '/../ClosureUse.php';
if (false) {
// For classmap-authoritative support.
/**
* For classmap-authoritative support.
*
* @deprecated use \PhpParser\Node\ClosureUse instead.
*/
class ClosureUse extends \PhpParser\Node\ClosureUse {
}
}

View File

@@ -77,7 +77,7 @@ class Param extends NodeAbstract {
return true;
}
if ($this->hooks === []) {
if (!$this->isPromoted()) {
return false;
}

View File

@@ -5,7 +5,11 @@ namespace PhpParser\Node\Scalar;
require __DIR__ . '/Float_.php';
if (false) {
// For classmap-authoritative support.
/**
* For classmap-authoritative support.
*
* @deprecated use \PhpParser\Node\Scalar\Float_ instead.
*/
class DNumber extends Float_ {
}
}

View File

@@ -5,7 +5,11 @@ namespace PhpParser\Node\Scalar;
require __DIR__ . '/InterpolatedString.php';
if (false) {
// For classmap-authoritative support.
/**
* For classmap-authoritative support.
*
* @deprecated use \PhpParser\Node\Scalar\InterpolatedString instead.
*/
class Encapsed extends InterpolatedString {
}
}

View File

@@ -7,7 +7,11 @@ use PhpParser\Node\InterpolatedStringPart;
require __DIR__ . '/../InterpolatedStringPart.php';
if (false) {
// For classmap-authoritative support.
/**
* For classmap-authoritative support.
*
* @deprecated use \PhpParser\Node\InterpolatedStringPart instead.
*/
class EncapsedStringPart extends InterpolatedStringPart {
}
}

View File

@@ -5,7 +5,11 @@ namespace PhpParser\Node\Scalar;
require __DIR__ . '/Int_.php';
if (false) {
// For classmap-authoritative support.
/**
* For classmap-authoritative support.
*
* @deprecated use \PhpParser\Node\Scalar\Int_ instead.
*/
class LNumber extends Int_ {
}
}

View File

@@ -124,7 +124,7 @@ class String_ extends Scalar {
// If it overflowed to float, treat as INT_MAX, it will throw an error anyway.
return self::codePointToUtf8(\is_int($dec) ? $dec : \PHP_INT_MAX);
} else {
return chr(octdec($str));
return chr(octdec($str) & 255);
}
},
$str

View File

@@ -7,7 +7,11 @@ use PhpParser\Node\DeclareItem;
require __DIR__ . '/../DeclareItem.php';
if (false) {
// For classmap-authoritative support.
/**
* For classmap-authoritative support.
*
* @deprecated use \PhpParser\Node\DeclareItem instead.
*/
class DeclareDeclare extends DeclareItem {
}
}

View File

@@ -7,7 +7,11 @@ use PhpParser\Node\PropertyItem;
require __DIR__ . '/../PropertyItem.php';
if (false) {
// For classmap-authoritative support.
/**
* For classmap-authoritative support.
*
* @deprecated use \PhpParser\Node\PropertyItem instead.
*/
class PropertyProperty extends PropertyItem {
}
}

View File

@@ -5,7 +5,11 @@ namespace PhpParser\Node\Stmt;
require __DIR__ . '/../StaticVar.php';
if (false) {
// For classmap-authoritative support.
/**
* For classmap-authoritative support.
*
* @deprecated use \PhpParser\Node\StaticVar instead.
*/
class StaticVar extends \PhpParser\Node\StaticVar {
}
}

View File

@@ -7,7 +7,11 @@ use PhpParser\Node\UseItem;
require __DIR__ . '/../UseItem.php';
if (false) {
// For classmap-authoritative support.
/**
* For classmap-authoritative support.
*
* @deprecated use \PhpParser\Node\UseItem instead.
*/
class UseUse extends UseItem {
}
}

View File

@@ -2478,7 +2478,9 @@ class Php7 extends \PhpParser\ParserAbstract
$self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]));
},
487 => static function ($self, $stackPos) {
$self->semValue = new Expr\Cast\Int_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]));
$attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]);
$attrs['kind'] = $self->getIntCastKind($self->semStack[$stackPos-(2-1)]);
$self->semValue = new Expr\Cast\Int_($self->semStack[$stackPos-(2-2)], $attrs);
},
488 => static function ($self, $stackPos) {
$attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]);
@@ -2486,7 +2488,9 @@ class Php7 extends \PhpParser\ParserAbstract
$self->semValue = new Expr\Cast\Double($self->semStack[$stackPos-(2-2)], $attrs);
},
489 => static function ($self, $stackPos) {
$self->semValue = new Expr\Cast\String_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]));
$attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]);
$attrs['kind'] = $self->getStringCastKind($self->semStack[$stackPos-(2-1)]);
$self->semValue = new Expr\Cast\String_($self->semStack[$stackPos-(2-2)], $attrs);
},
490 => static function ($self, $stackPos) {
$self->semValue = new Expr\Cast\Array_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]));
@@ -2495,7 +2499,9 @@ class Php7 extends \PhpParser\ParserAbstract
$self->semValue = new Expr\Cast\Object_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]));
},
492 => static function ($self, $stackPos) {
$self->semValue = new Expr\Cast\Bool_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]));
$attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]);
$attrs['kind'] = $self->getBoolCastKind($self->semStack[$stackPos-(2-1)]);
$self->semValue = new Expr\Cast\Bool_($self->semStack[$stackPos-(2-2)], $attrs);
},
493 => static function ($self, $stackPos) {
$self->semValue = new Expr\Cast\Unset_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]));
@@ -2686,10 +2692,10 @@ class Php7 extends \PhpParser\ParserAbstract
561 => static function ($self, $stackPos) {
$attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Expr\Array_::KIND_LONG;
$self->semValue = new Expr\Array_($self->semStack[$stackPos-(4-3)], $attrs);
$self->createdArrays->attach($self->semValue);
$self->createdArrays->offsetSet($self->semValue);
},
562 => static function ($self, $stackPos) {
$self->semValue = $self->semStack[$stackPos-(1-1)]; $self->createdArrays->attach($self->semValue);
$self->semValue = $self->semStack[$stackPos-(1-1)]; $self->createdArrays->offsetSet($self->semValue);
},
563 => static function ($self, $stackPos) {
$self->semValue = Scalar\String_::fromString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]), $self->phpVersion->supportsUnicodeEscapes());

View File

@@ -2479,7 +2479,9 @@ class Php8 extends \PhpParser\ParserAbstract
$self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]));
},
490 => static function ($self, $stackPos) {
$self->semValue = new Expr\Cast\Int_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]));
$attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]);
$attrs['kind'] = $self->getIntCastKind($self->semStack[$stackPos-(2-1)]);
$self->semValue = new Expr\Cast\Int_($self->semStack[$stackPos-(2-2)], $attrs);
},
491 => static function ($self, $stackPos) {
$attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]);
@@ -2487,7 +2489,9 @@ class Php8 extends \PhpParser\ParserAbstract
$self->semValue = new Expr\Cast\Double($self->semStack[$stackPos-(2-2)], $attrs);
},
492 => static function ($self, $stackPos) {
$self->semValue = new Expr\Cast\String_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]));
$attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]);
$attrs['kind'] = $self->getStringCastKind($self->semStack[$stackPos-(2-1)]);
$self->semValue = new Expr\Cast\String_($self->semStack[$stackPos-(2-2)], $attrs);
},
493 => static function ($self, $stackPos) {
$self->semValue = new Expr\Cast\Array_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]));
@@ -2496,7 +2500,9 @@ class Php8 extends \PhpParser\ParserAbstract
$self->semValue = new Expr\Cast\Object_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]));
},
495 => static function ($self, $stackPos) {
$self->semValue = new Expr\Cast\Bool_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]));
$attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]);
$attrs['kind'] = $self->getBoolCastKind($self->semStack[$stackPos-(2-1)]);
$self->semValue = new Expr\Cast\Bool_($self->semStack[$stackPos-(2-2)], $attrs);
},
496 => static function ($self, $stackPos) {
$self->semValue = new Expr\Cast\Unset_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]));
@@ -2687,10 +2693,10 @@ class Php8 extends \PhpParser\ParserAbstract
564 => static function ($self, $stackPos) {
$attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Expr\Array_::KIND_LONG;
$self->semValue = new Expr\Array_($self->semStack[$stackPos-(4-3)], $attrs);
$self->createdArrays->attach($self->semValue);
$self->createdArrays->offsetSet($self->semValue);
},
565 => static function ($self, $stackPos) {
$self->semValue = $self->semStack[$stackPos-(1-1)]; $self->createdArrays->attach($self->semValue);
$self->semValue = $self->semStack[$stackPos-(1-1)]; $self->createdArrays->offsetSet($self->semValue);
},
566 => static function ($self, $stackPos) {
$self->semValue = Scalar\String_::fromString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]), $self->phpVersion->supportsUnicodeEscapes());

View File

@@ -736,6 +736,33 @@ abstract class ParserAbstract implements Parser {
return Double::KIND_DOUBLE;
}
protected function getIntCastKind(string $cast): int {
$cast = strtolower($cast);
if (strpos($cast, 'integer') !== false) {
return Expr\Cast\Int_::KIND_INTEGER;
}
return Expr\Cast\Int_::KIND_INT;
}
protected function getBoolCastKind(string $cast): int {
$cast = strtolower($cast);
if (strpos($cast, 'boolean') !== false) {
return Expr\Cast\Bool_::KIND_BOOLEAN;
}
return Expr\Cast\Bool_::KIND_BOOL;
}
protected function getStringCastKind(string $cast): int {
$cast = strtolower($cast);
if (strpos($cast, 'binary') !== false) {
return Expr\Cast\String_::KIND_BINARY;
}
return Expr\Cast\String_::KIND_STRING;
}
/** @param array<string, mixed> $attributes */
protected function parseLNumber(string $str, array $attributes, bool $allowInvalidOctal = false): Int_ {
try {
@@ -976,7 +1003,7 @@ abstract class ParserAbstract implements Parser {
}
protected function fixupArrayDestructuring(Array_ $node): Expr\List_ {
$this->createdArrays->detach($node);
$this->createdArrays->offsetUnset($node);
return new Expr\List_(array_map(function (Node\ArrayItem $item) {
if ($item->value instanceof Expr\Error) {
// We used Error as a placeholder for empty elements, which are legal for destructuring.