From 74463a06977e8ff949fd4edfcee6053d13275db0 Mon Sep 17 00:00:00 2001 From: Anton Vasiliev Date: Sat, 24 Apr 2021 17:10:46 +0100 Subject: [PATCH 1/3] #733 - Refactor ReadDetector class --- Library/Detectors/ReadDetector.php | 54 ++++++++++++++++-------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/Library/Detectors/ReadDetector.php b/Library/Detectors/ReadDetector.php index ede40e043b..c3dbf590ba 100644 --- a/Library/Detectors/ReadDetector.php +++ b/Library/Detectors/ReadDetector.php @@ -9,10 +9,15 @@ * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Zephir\Detectors; use Zephir\Variable; +use function in_array; +use function is_array; + /** * ReadDetector. * @@ -23,52 +28,49 @@ */ class ReadDetector { - public function detect($variable, array $expression) + // TODO: Move params from detect() to constructor() + + /** + * @param string $variable + * @param array $expression + * @return bool + */ + public function detect(string $variable, array $expression): bool { if (!isset($expression['type'])) { return false; } - /* Remove branch from variable name */ + /** + * Remove branch from variable name + */ $pos = strpos($variable, Variable::BRANCH_MAGIC); if ($pos > -1) { $variable = substr($variable, 0, $pos); } - if ('variable' == $expression['type']) { - if ($variable == $expression['value']) { - return true; - } + if ('variable' === $expression['type'] && $variable === $expression['value']) { + return true; } - if ('fcall' == $expression['type'] || 'mcall' == $expression['type'] || 'scall' == $expression['type']) { - if (isset($expression['parameters'])) { - foreach ($expression['parameters'] as $parameter) { - if (\is_array($parameter['parameter'])) { - if ('variable' == $parameter['parameter']['type']) { - if ($variable == $parameter['parameter']['value']) { - return true; - } + if (in_array($expression['type'], ['fcall', 'mcall', 'scall']) && isset($expression['parameters'])) { + foreach ($expression['parameters'] as $parameter) { + if (is_array($parameter['parameter'])) { + if ('variable' === $parameter['parameter']['type']) { + if ($variable === $parameter['parameter']['value']) { + return true; } } } } } - if (isset($expression['left'])) { - if (\is_array($expression['left'])) { - if (true === $this->detect($variable, $expression['left'])) { - return true; - } - } + if (isset($expression['left']) && is_array($expression['left']) && $this->detect($variable, $expression['left'])) { + return true; } - if (isset($expression['right'])) { - if (\is_array($expression['right'])) { - if (true === $this->detect($variable, $expression['right'])) { - return true; - } - } + if (isset($expression['right']) && is_array($expression['right']) && $this->detect($variable, $expression['right'])) { + return true; } return false; From 2be4676174567d0928ac1f81b2e7ca5bb1b96583 Mon Sep 17 00:00:00 2001 From: Anton Vasiliev Date: Sat, 24 Apr 2021 19:25:40 +0100 Subject: [PATCH 2/3] #733 - Mass PHP code refactor --- Library/Expression.php | 75 ++-- .../FunctionCall/ImplodeOptimizer.php | 12 +- Library/Passes/LocalContextPass.php | 95 ++--- Library/StatementsBlock.php | 42 +- Library/StaticCall.php | 374 +++++++++--------- Library/SymbolTable.php | 314 ++++++++------- Library/Variable.php | 2 +- 7 files changed, 472 insertions(+), 442 deletions(-) diff --git a/Library/Expression.php b/Library/Expression.php index bea5ddc124..1940fd6114 100644 --- a/Library/Expression.php +++ b/Library/Expression.php @@ -11,6 +11,7 @@ namespace Zephir; +use ReflectionException; use Zephir\Exception\CompilerException; use Zephir\Expression\Closure; use Zephir\Expression\ClosureArrow; @@ -64,6 +65,8 @@ use Zephir\Operators\Unary\MinusOperator; use Zephir\Operators\Unary\NotOperator; +use function strlen; + /** * Zephir\Expressions. * @@ -71,25 +74,26 @@ */ class Expression { - protected $expression; + protected array $expression; - protected $expecting = true; + protected bool $expecting = true; - protected $readOnly = false; + protected bool $readOnly = false; - protected $noisy = true; + protected bool $noisy = true; /** * @deprecated - * * @var bool */ - protected $stringOperation = false; + protected bool $stringOperation = false; - /** @var Variable */ - protected $expectingVariable; + /** + * @var Variable|null + */ + protected ?Variable $expectingVariable = null; - protected $evalMode = false; + protected bool $evalMode = false; /** * Expression constructor. @@ -106,7 +110,7 @@ public function __construct(array $expression) * * @return array */ - public function getExpression() + public function getExpression(): array { return $this->expression; } @@ -115,10 +119,10 @@ public function getExpression() * Sets if the variable must be resolved into a direct variable symbol * create a temporary value or ignore the return value. * - * @param bool $expecting - * @param Variable $expectingVariable + * @param bool $expecting + * @param Variable|null $expectingVariable */ - public function setExpectReturn($expecting, Variable $expectingVariable = null) + public function setExpectReturn(bool $expecting, Variable $expectingVariable = null) { $this->expecting = $expecting; $this->expectingVariable = $expectingVariable; @@ -129,7 +133,7 @@ public function setExpectReturn($expecting, Variable $expectingVariable = null) * * @param bool $readOnly */ - public function setReadOnly($readOnly) + public function setReadOnly(bool $readOnly): void { $this->readOnly = $readOnly; } @@ -139,7 +143,7 @@ public function setReadOnly($readOnly) * * @return bool */ - public function isReadOnly() + public function isReadOnly(): bool { return $this->readOnly; } @@ -150,7 +154,7 @@ public function isReadOnly() * * @return bool */ - public function isExpectingReturn() + public function isExpectingReturn(): bool { return $this->expecting; } @@ -159,9 +163,9 @@ public function isExpectingReturn() * Returns the variable which is expected to return the * result of the expression evaluation. * - * @return Variable + * @return Variable|null */ - public function getExpectingVariable() + public function getExpectingVariable(): ?Variable { return $this->expectingVariable; } @@ -171,7 +175,7 @@ public function getExpectingVariable() * * @param bool $noisy */ - public function setNoisy($noisy) + public function setNoisy(bool $noisy): void { $this->noisy = $noisy; } @@ -181,7 +185,7 @@ public function setNoisy($noisy) * * @return bool */ - public function isNoisy() + public function isNoisy(): bool { return $this->noisy; } @@ -194,7 +198,7 @@ public function isNoisy() * * @param bool $stringOperation */ - public function setStringOperation($stringOperation) + public function setStringOperation(bool $stringOperation): void { $this->stringOperation = $stringOperation; } @@ -207,7 +211,7 @@ public function setStringOperation($stringOperation) * * @return bool */ - public function isStringOperation() + public function isStringOperation(): bool { return $this->stringOperation; } @@ -217,7 +221,7 @@ public function isStringOperation() * * @param bool $evalMode */ - public function setEvalMode($evalMode) + public function setEvalMode(bool $evalMode): void { $this->evalMode = $evalMode; } @@ -230,9 +234,9 @@ public function setEvalMode($evalMode) * * @return CompiledExpression */ - public function emptyArray($expression, CompilationContext $compilationContext) + public function emptyArray(array $expression, CompilationContext $compilationContext): CompiledExpression { - /* + /** * Resolves the symbol that expects the value */ if ($this->expecting) { @@ -246,14 +250,14 @@ public function emptyArray($expression, CompilationContext $compilationContext) $symbolVariable = $compilationContext->symbolTable->getTempVariableForWrite('variable', $compilationContext, $expression); } - /* + /** * Variable that receives property accesses must be polymorphic */ if (!$symbolVariable->isVariable() && 'array' != $symbolVariable->getType()) { throw new CompilerException('Cannot use variable: '.$symbolVariable->getName().'('.$symbolVariable->getType().') to create empty array', $expression); } - /* + /** * Mark the variable as an 'array' */ $symbolVariable->setDynamicTypes('array'); @@ -268,9 +272,9 @@ public function emptyArray($expression, CompilationContext $compilationContext) * * @param CompilationContext $compilationContext * - * @throws CompilerException|Exception - * * @return CompiledExpression + * @throws Exception + * @throws ReflectionException */ public function compile(CompilationContext $compilationContext): CompiledExpression { @@ -297,11 +301,12 @@ public function compile(CompilationContext $compilationContext): CompiledExpress return new LiteralCompiledExpression('istring', str_replace(PHP_EOL, '\\n', $expression['value']), $expression); case 'char': - if (!\strlen($expression['value'])) { + if (!strlen($expression['value'])) { throw new CompilerException('Invalid empty char literal', $expression); } - if (\strlen($expression['value']) > 2) { - if (\strlen($expression['value']) > 10) { + + if (strlen($expression['value']) > 2) { + if (strlen($expression['value']) > 10) { throw new CompilerException("Invalid char literal: '".substr($expression['value'], 0, 10)."...'", $expression); } else { throw new CompilerException("Invalid char literal: '".$expression['value']."'", $expression); @@ -313,7 +318,7 @@ public function compile(CompilationContext $compilationContext): CompiledExpress case 'variable': $var = $compilationContext->symbolTable->getVariable($expression['value']); if ($var) { - if ('this' == $var->getRealName()) { + if ('this' === $var->getRealName()) { $var = 'this'; } else { $var = $var->getName(); @@ -501,12 +506,13 @@ public function compile(CompilationContext $compilationContext): CompiledExpress break; case 'list': - if ('list' == $expression['left']['type']) { + if ('list' === $expression['left']['type']) { $compilationContext->logger->warning( 'Unnecessary extra parentheses', ['extra-parentheses', $expression] ); } + $numberPrints = $compilationContext->codePrinter->getNumberPrints(); $expr = new self($expression['left']); $expr->setExpectReturn($this->expecting, $this->expectingVariable); @@ -594,6 +600,7 @@ public function compile(CompilationContext $compilationContext): CompiledExpress if (!$compilableExpression) { throw new CompilerException('Unknown expression passed as compilableExpression', $expression); } + $compilableExpression->setReadOnly($this->isReadOnly()); $compilableExpression->setExpectReturn($this->expecting, $this->expectingVariable); diff --git a/Library/Optimizers/FunctionCall/ImplodeOptimizer.php b/Library/Optimizers/FunctionCall/ImplodeOptimizer.php index 7d3be029b5..871d8998c3 100644 --- a/Library/Optimizers/FunctionCall/ImplodeOptimizer.php +++ b/Library/Optimizers/FunctionCall/ImplodeOptimizer.php @@ -9,15 +9,19 @@ * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Zephir\Optimizers\FunctionCall; -use function Zephir\add_slashes; use Zephir\Call; use Zephir\CompilationContext; use Zephir\CompiledExpression; use Zephir\Exception\CompilerException; use Zephir\Optimizers\OptimizerAbstract; +use function count; +use function Zephir\add_slashes; + /** * ImplodeOptimizer. * @@ -40,11 +44,11 @@ public function optimize(array $expression, Call $call, CompilationContext $cont return false; } - if (2 != \count($expression['parameters'])) { + if (2 !== count($expression['parameters'])) { return false; } - /* + /** * Process the expected symbol to be returned */ $call->processExpectedReturn($context); @@ -54,7 +58,7 @@ public function optimize(array $expression, Call $call, CompilationContext $cont throw new CompilerException('Returned values by functions can only be assigned to variant variables', $expression); } - if ('string' == $expression['parameters'][0]['parameter']['type']) { + if ('string' === $expression['parameters'][0]['parameter']['type']) { $str = add_slashes($expression['parameters'][0]['parameter']['value']); unset($expression['parameters'][0]); } diff --git a/Library/Passes/LocalContextPass.php b/Library/Passes/LocalContextPass.php index d09296eb61..28a127e431 100644 --- a/Library/Passes/LocalContextPass.php +++ b/Library/Passes/LocalContextPass.php @@ -9,6 +9,8 @@ * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Zephir\Passes; use Zephir\StatementsBlock; @@ -17,26 +19,26 @@ * LocalContextPass. * * This pass tries to check whether variables only do exist in the local context of the method block - * or if they're used externally which will unallow variables to be placed in the stack + * or if they're used externally which will disallow variables to be placed in the stack. * * This pass also tracks the number of initializations a variable may have, this allows * to mark variables as read-only after their last initialization. The number of * mutations is relative, since assignments inside cycles/loops may perform a n-number of - * mutations + * mutations. * * @see http://en.wikipedia.org/wiki/Escape_analysis */ class LocalContextPass { - protected $variables = []; + protected array $variables = []; - protected $mutations = []; + protected array $mutations = []; - protected $uses = []; + protected array $uses = []; - protected $lastCallLine = false; + protected int $lastCallLine = 0; - protected $lastUnsetLine = false; + protected int $lastUnsetLine = 0; /** * Do the compilation pass. @@ -48,13 +50,12 @@ public function pass(StatementsBlock $block) $this->passStatementBlock($block->getStatements()); } - public function declareVariables(array $statement) + public function declareVariables(array $statement): void { - if (isset($statement['data-type'])) { - if ('variable' != $statement['data-type']) { - return; - } + if (isset($statement['data-type']) && 'variable' !== $statement['data-type']) { + return; } + foreach ($statement['variables'] as $variable) { if (isset($variable['expr'])) { if ('string' == $variable['expr']['type'] || 'empty-array' == $variable['expr']['type'] || 'array' == $variable['expr']['type']) { @@ -62,6 +63,7 @@ public function declareVariables(array $statement) continue; } } + if (!isset($this->variables[$variable['variable']])) { $this->variables[$variable['variable']] = true; } @@ -73,7 +75,7 @@ public function declareVariables(array $statement) * * @param string $variable */ - public function markVariableNoLocal($variable) + public function markVariableNoLocal(string $variable): void { if (isset($this->variables[$variable])) { $this->variables[$variable] = false; @@ -86,7 +88,7 @@ public function markVariableNoLocal($variable) * @param string $variable * @param array $node */ - public function markLastUse($variable, $node) + public function markLastUse(string $variable, array $node): void { if (isset($node['line'])) { $this->uses[$variable] = $node['line']; @@ -100,15 +102,9 @@ public function markLastUse($variable, $node) * * @return bool */ - public function shouldBeLocal($variable) + public function shouldBeLocal(string $variable): bool { - if (isset($this->variables[$variable])) { - if ($this->variables[$variable]) { - return true; - } - } - - return false; + return !empty($this->variables[$variable]); } /** @@ -116,7 +112,7 @@ public function shouldBeLocal($variable) * * @param string $variable */ - public function increaseMutations($variable) + public function increaseMutations(string $variable): void { if (isset($this->mutations[$variable])) { ++$this->mutations[$variable]; @@ -132,7 +128,7 @@ public function increaseMutations($variable) * * @return int */ - public function getNumberOfMutations($variable) + public function getNumberOfMutations(string $variable): int { if (isset($this->mutations[$variable])) { return $this->mutations[$variable]; @@ -148,7 +144,7 @@ public function getNumberOfMutations($variable) * * @return int */ - public function getLastVariableUseLine($variable) + public function getLastVariableUseLine(string $variable): int { if (isset($this->uses[$variable])) { return $this->uses[$variable]; @@ -162,7 +158,7 @@ public function getLastVariableUseLine($variable) * * @return int */ - public function getLastCallLine() + public function getLastCallLine(): int { return $this->lastCallLine; } @@ -172,7 +168,7 @@ public function getLastCallLine() * * @return int */ - public function getLastUnsetLine() + public function getLastUnsetLine(): int { return $this->lastUnsetLine; } @@ -183,6 +179,7 @@ public function passLetStatement(array $statement) if (isset($assignment['expr'])) { $this->passExpression($assignment['expr']); } + $this->increaseMutations($assignment['variable']); switch ($assignment['assign-type']) { @@ -279,23 +276,25 @@ public function passLetStatement(array $statement) } } - public function passCall(array $expression) + public function passCall(array $expression): void { - if (isset($expression['parameters'])) { - foreach ($expression['parameters'] as $parameter) { - if ('variable' == $parameter['parameter']['type']) { - $this->markVariableNoLocal($parameter['parameter']['value']); - } else { - $this->passExpression($parameter['parameter']); - } + if (!isset($expression['parameters'])) { + return; + } + + foreach ($expression['parameters'] as $parameter) { + if ('variable' === $parameter['parameter']['type']) { + $this->markVariableNoLocal($parameter['parameter']['value']); + } else { + $this->passExpression($parameter['parameter']); } } } - public function passArray(array $expression) + public function passArray(array $expression): void { foreach ($expression['left'] as $item) { - if ('variable' == $item['value']['type']) { + if ('variable' === $item['value']['type']) { $this->markVariableNoLocal($item['value']['value']); } else { $this->passExpression($item['value']); @@ -303,20 +302,22 @@ public function passArray(array $expression) } } - public function passNew(array $expression) + public function passNew(array $expression): void { - if (isset($expression['parameters'])) { - foreach ($expression['parameters'] as $parameter) { - if ('variable' == $parameter['parameter']['type']) { - $this->markVariableNoLocal($parameter['parameter']['value']); - } else { - $this->passExpression($parameter['parameter']); - } + if (!isset($expression['parameters'])) { + return; + } + + foreach ($expression['parameters'] as $parameter) { + if ('variable' == $parameter['parameter']['type']) { + $this->markVariableNoLocal($parameter['parameter']['value']); + } else { + $this->passExpression($parameter['parameter']); } } } - public function passExpression(array $expression) + public function passExpression(array $expression): void { switch ($expression['type']) { case 'bool': @@ -449,7 +450,7 @@ public function passExpression(array $expression) } } - public function passStatementBlock(array $statements) + public function passStatementBlock(array $statements): void { foreach ($statements as $statement) { switch ($statement['type']) { diff --git a/Library/StatementsBlock.php b/Library/StatementsBlock.php index 60e3d86dce..a8cae86933 100644 --- a/Library/StatementsBlock.php +++ b/Library/StatementsBlock.php @@ -11,6 +11,7 @@ namespace Zephir; +use ReflectionException; use Zephir\Passes\MutateGathererPass; use Zephir\Statements\BreakStatement; use Zephir\Statements\ContinueStatement; @@ -29,6 +30,8 @@ use Zephir\Statements\UnsetStatement; use Zephir\Statements\WhileStatement; +use function count; + /** * StatementsBlock. * @@ -41,9 +44,9 @@ class StatementsBlock protected $unreachable; - protected $debug = false; + protected bool $debug = false; - protected $loop = false; + protected bool $loop = false; protected $mutateGatherer; @@ -85,7 +88,7 @@ public function __construct(array $statements) * * @return StatementsBlock */ - public function isLoop($loop) + public function isLoop(bool $loop): StatementsBlock { $this->loop = $loop; @@ -94,12 +97,14 @@ public function isLoop($loop) /** * @param CompilationContext $compilationContext - * @param bool $unreachable - * @param int $branchType + * @param bool $unreachable + * @param int $branchType * * @return Branch + * @throws Exception + * @throws ReflectionException */ - public function compile(CompilationContext $compilationContext, $unreachable = false, $branchType = Branch::TYPE_UNKNOWN) + public function compile(CompilationContext $compilationContext, $unreachable = false, $branchType = Branch::TYPE_UNKNOWN): Branch { $compilationContext->codePrinter->increaseLevel(); ++$compilationContext->currentBranch; @@ -111,16 +116,14 @@ public function compile(CompilationContext $compilationContext, $unreachable = f $currentBranch->setType($branchType); $currentBranch->setUnreachable($unreachable); - /* + /** * Activate branch in the branch manager */ $compilationContext->branchManager->addBranch($currentBranch); - $this->unreachable = $unreachable; - $statements = $this->statements; - /* + /** * Reference the block if it belongs to a loop */ if ($this->loop) { @@ -140,7 +143,7 @@ public function compile(CompilationContext $compilationContext, $unreachable = f } foreach ($statements as $statement) { - /* + /** * TODO: Generate GDB hints */ if ($this->debug) { @@ -153,7 +156,7 @@ public function compile(CompilationContext $compilationContext, $unreachable = f } } - /* + /** * Show warnings if code is generated when the 'unreachable state' is 'on' */ if (true === $this->unreachable) { @@ -347,19 +350,18 @@ public function compile(CompilationContext $compilationContext, $unreachable = f } } - /* + /** * Reference the block if it belongs to a loop */ if ($this->loop) { array_pop($compilationContext->cycleBlocks); } - /* + /** * Traverses temporal variables created in a specific branch * marking them as idle */ $compilationContext->symbolTable->markTemporalVariablesIdle($compilationContext); - $compilationContext->branchManager->removeBranch($currentBranch); --$compilationContext->currentBranch; @@ -373,7 +375,7 @@ public function compile(CompilationContext $compilationContext, $unreachable = f * * @return array */ - public function getStatements() + public function getStatements(): array { return $this->statements; } @@ -393,7 +395,7 @@ public function setStatements(array $statements) * * @return string */ - public function getLastStatementType() + public function getLastStatementType(): string { return $this->lastStatement['type']; } @@ -413,9 +415,9 @@ public function getLastStatement() * * @return bool */ - public function isEmpty() + public function isEmpty(): bool { - return 0 == \count($this->statements); + return 0 === count($this->statements); } /** @@ -425,7 +427,7 @@ public function isEmpty() * * @return MutateGathererPass */ - public function getMutateGatherer($pass = false) + public function getMutateGatherer(bool $pass = false): MutateGathererPass { if (!$this->mutateGatherer) { $this->mutateGatherer = new MutateGathererPass(); diff --git a/Library/StaticCall.php b/Library/StaticCall.php index 3099f93750..de79deca8e 100644 --- a/Library/StaticCall.php +++ b/Library/StaticCall.php @@ -9,11 +9,18 @@ * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Zephir; +use ReflectionException; use Zephir\Detectors\ReadDetector; use Zephir\Exception\CompilerException; +use function count; +use function in_array; +use function is_string; + /** * StaticCall. * @@ -24,26 +31,20 @@ class StaticCall extends Call /** * Compiles a static method call. * - * @param Expression $expr + * @param Expression $expr * @param CompilationContext $compilationContext * - * @throws CompilerException - * * @return CompiledExpression + * @throws Exception + * @throws ReflectionException */ - public function compile(Expression $expr, CompilationContext $compilationContext) + public function compile(Expression $expr, CompilationContext $compilationContext): CompiledExpression { $expression = $expr->getExpression(); - $methodName = strtolower($expression['name']); - - if (isset($expression['dynamic'])) { - $dynamicMethod = $expression['dynamic']; - } else { - $dynamicMethod = false; - } - + $dynamicMethod = $expression['dynamic'] ?? false; $symbolVariable = null; + $method = null; /** * Create temporary variable if needed. @@ -52,27 +53,22 @@ public function compile(Expression $expr, CompilationContext $compilationContext $isExpecting = $expr->isExpectingReturn(); if ($isExpecting) { $symbolVariable = $expr->getExpectingVariable(); - if (\is_object($symbolVariable)) { - $readDetector = new ReadDetector($expression); + if ($symbolVariable instanceof Variable) { + $readDetector = new ReadDetector(); if ($readDetector->detect($symbolVariable->getName(), $expression)) { - $symbolVariable = $compilationContext->symbolTable->getTempVariableForObserveOrNullify('variable', $compilationContext, $expression); + $symbolVariable = $compilationContext->symbolTable->getTempVariableForObserveOrNullify('variable', $compilationContext); } else { $mustInit = true; } } else { - $symbolVariable = $compilationContext->symbolTable->getTempVariableForObserveOrNullify('variable', $compilationContext, $expression); + $symbolVariable = $compilationContext->symbolTable->getTempVariableForObserveOrNullify('variable', $compilationContext); } - } - /** - * Method calls only return zvals so we need to validate the target variable is also a zval - */ - if ($isExpecting) { /** + * Method calls only return zvals so we need to validate the target variable is also a zval. * At this point, we don't know the exact dynamic type returned by the static method call */ $symbolVariable->setDynamicTypes('undefined'); - if (!$symbolVariable->isVariable()) { throw new CompilerException('Returned values by functions can only be assigned to variant variables', $expression); } @@ -90,8 +86,8 @@ public function compile(Expression $expr, CompilationContext $compilationContext $className = $expression['class']; $classDefinition = false; - if (!\in_array($className, ['self', 'static', 'parent'])) { - if (\is_string($className)) { + if (!in_array($className, ['self', 'static', 'parent'])) { + if (is_string($className)) { $className = $compilationContext->getFullName($className); if ($compiler->isClass($className)) { $classDefinition = $compiler->getClassDefinition($className); @@ -113,7 +109,7 @@ public function compile(Expression $expr, CompilationContext $compilationContext } } } else { - if ('parent' == $className) { + if ('parent' === $className) { $classDefinition = $compilationContext->classDefinition; $extendsClass = $classDefinition->getExtendsClass(); if (!$extendsClass) { @@ -134,12 +130,11 @@ public function compile(Expression $expr, CompilationContext $compilationContext // TODO: Consider to check instance of ClassDefinitionRuntime and throw another error, telling that class was not found. // TODO: This will give false if external class does not exists! if (!$classDefinition->hasMethod($methodName)) { - $possibleMethod = $classDefinition->getPossibleMethodName($methodName); - if ($possibleMethod) { - throw new CompilerException("Class '".$classDefinition->getCompleteName()."' does not implement static method: '".$expression['name']."'. Did you mean '".$possibleMethod."'?", $expression); - } else { - throw new CompilerException("Class '".$classDefinition->getCompleteName()."' does not implement static method: '".$expression['name']."'", $expression); + if ($classDefinition->getPossibleMethodName($methodName)) { + throw new CompilerException("Class '".$classDefinition->getCompleteName()."' does not implement static method: '".$expression['name']."'.", $expression); } + + throw new CompilerException("Class '".$classDefinition->getCompleteName()."' does not implement static method: '".$expression['name']."'", $expression); } else { $method = $classDefinition->getMethod($methodName); @@ -147,7 +142,7 @@ public function compile(Expression $expr, CompilationContext $compilationContext throw new CompilerException("Cannot call private method '".$methodName."' out of its scope", $expression); } - if (!\in_array($className, ['self', 'static', 'parent'])) { + if (!in_array($className, ['self', 'static', 'parent'])) { if (!$method->isStatic()) { throw new CompilerException("Cannot call non-static method '".$methodName."' in a static way", $expression); } @@ -158,11 +153,7 @@ public function compile(Expression $expr, CompilationContext $compilationContext /** * Try to produce an exception if method is called with a wrong number of parameters */ - if (isset($expression['parameters'])) { - $callNumberParameters = \count($expression['parameters']); - } else { - $callNumberParameters = 0; - } + $callNumberParameters = isset($expression['parameters']) ? count($expression['parameters']) : 0; $classMethod = $classDefinition->getMethod($methodName); $expectedNumberParameters = $classMethod->getNumberOfRequiredParameters(); @@ -189,7 +180,7 @@ public function compile(Expression $expr, CompilationContext $compilationContext } } - /* + /** * Call static methods in the same class, use the special context 'self' or special context 'static' * Call static methods in the 'self' context */ @@ -197,13 +188,13 @@ public function compile(Expression $expr, CompilationContext $compilationContext if ($dynamicClass) { $this->callFromDynamicClass($methodName, $expression, $symbolVariable, $mustInit, $isExpecting, $compilationContext); } else { - if (\in_array($className, ['self', 'static']) || $classDefinition == $compilationContext->classDefinition) { - $this->call(strtoupper($className), $methodName, $expression, $symbolVariable, $mustInit, $isExpecting, $classDefinition, $compilationContext, isset($method) ? $method : null); + if (in_array($className, ['self', 'static']) || $classDefinition == $compilationContext->classDefinition) { + $this->call(strtoupper($className), $methodName, $expression, $symbolVariable, $mustInit, $isExpecting, $compilationContext, $method ?? null); } else { - if ('parent' == $className) { - $this->callParent($methodName, $expression, $symbolVariable, $mustInit, $isExpecting, $currentClassDefinition, $compilationContext, isset($method) ? $method : null); + if ('parent' === $className) { + $this->callParent($methodName, $expression, $symbolVariable, $mustInit, $isExpecting, $currentClassDefinition, $compilationContext, $method ?? null); } else { - $this->callFromClass($methodName, $expression, $symbolVariable, $mustInit, $isExpecting, $classDefinition, $compilationContext, isset($method) ? $method : null); + $this->callFromClass($methodName, $expression, $symbolVariable, $mustInit, $isExpecting, $classDefinition, $compilationContext, $method ?? null); } } } @@ -213,36 +204,26 @@ public function compile(Expression $expr, CompilationContext $compilationContext } } - /* + /** * Add the last call status to the current symbol table */ $this->addCallStatusFlag($compilationContext); - /* + /** * Transfer the return type-hint to the returned variable */ - if ($isExpecting) { - if (isset($method)) { - if ($method instanceof ClassMethod) { - $returnClassTypes = $method->getReturnClassTypes(); - if (null !== $returnClassTypes) { - $symbolVariable->setDynamicTypes('object'); - foreach ($returnClassTypes as $classType) { - $symbolVariable->setClassTypes($compilationContext->getFullName($classType)); - } - } + if ($isExpecting && $method instanceof ClassMethod) { + $symbolVariable->setDynamicTypes('object'); + foreach ($method->getReturnClassTypes() as $classType) { + $symbolVariable->setClassTypes($compilationContext->getFullName($classType)); + } - $returnTypes = $method->getReturnTypes(); - if (null !== $returnTypes) { - foreach ($returnTypes as $dataType => $returnType) { - $symbolVariable->setDynamicTypes($dataType); - } - } - } + foreach ($method->getReturnTypes() as $dataType => $returnType) { + $symbolVariable->setDynamicTypes($dataType); } } - /* + /** * We can mark temporary variables generated as idle here */ foreach ($this->getTemporalVariables() as $tempVariable) { @@ -259,39 +240,39 @@ public function compile(Expression $expr, CompilationContext $compilationContext /** * Calls static methods on the 'self/static' context. * - * @param string $context SELF / STATIC - * @param string $methodName - * @param array $expression - * @param Variable $symbolVariable - * @param bool $mustInit - * @param bool $isExpecting - * @param ClassDefinition $classDefinition + * @param string $context SELF / STATIC + * @param string $methodName + * @param array $expression + * @param Variable|null $symbolVariable + * @param bool $mustInit + * @param bool $isExpecting * @param CompilationContext $compilationContext - * @param ClassMethod $method + * @param ClassMethod|null $method + * + * @throws Exception */ protected function call( - $context, - $methodName, + string $context, + string $methodName, array $expression, - $symbolVariable, - $mustInit, - $isExpecting, - ClassDefinition $classDefinition, + ?Variable $symbolVariable = null, + bool $mustInit, + bool $isExpecting, CompilationContext $compilationContext, - ClassMethod $method - ) { - if (!\in_array($context, ['SELF', 'STATIC'])) { + ?ClassMethod $method = null + ): void { + if (!in_array($context, ['SELF', 'STATIC'])) { $context = 'SELF'; } - /* Do not optimize static:: calls, to allow late static binding */ - if ('SELF' == $context) { + /** + * Do not optimize static:: calls, to allow late static binding + */ + if ('SELF' === $context) { $method = $method->getOptimizedMethod(); } - $codePrinter = $compilationContext->codePrinter; - - /* + /** * Call static methods must grown the stack */ $compilationContext->symbolTable->mustGrownStack(true); @@ -302,59 +283,51 @@ protected function call( } /** - * Check if the method call can have an inline cache. + * Check if the method call can have an inline cache. */ $methodCache = $compilationContext->cacheManager->getStaticMethodCache(); - $cachePointer = $methodCache->get($compilationContext, isset($method) ? $method : null, false); - - if (isset($expression['parameters']) && \count($expression['parameters'])) { - $params = $this->getResolvedParams($expression['parameters'], $compilationContext, $expression); - } else { - $params = []; - } - - $isInternal = false; - if (isset($method)) { - $isInternal = $method->isInternal(); - } + $cachePointer = $methodCache->get($compilationContext, $method ?? null, false); if ($symbolVariable) { $symbol = $compilationContext->backend->getVariableCodePointer($symbolVariable); } - $paramCount = \count($params); - $paramsStr = $paramCount ? ', '.implode(', ', $params) : ''; - if (!$isInternal) { + $params = $this->getResolvedParameters($expression, $compilationContext); + $paramCount = count($params); + $paramsStr = $paramCount > 0 ? ', '.implode(', ', $params) : ''; + + $isInternal = $method instanceof ClassMethod && $method->isInternal(); + if (false === $isInternal) { if ($isExpecting) { - if ('return_value' == $symbolVariable->getName()) { - $codePrinter->output('ZEPHIR_RETURN_CALL_'.$context.'("'.$methodName.'", '.$cachePointer.$paramsStr.');'); + if ('return_value' === $symbolVariable->getName()) { + $compilationContext->codePrinter->output('ZEPHIR_RETURN_CALL_'.$context.'("'.$methodName.'", '.$cachePointer.$paramsStr.');'); } else { - $codePrinter->output('ZEPHIR_CALL_'.$context.'('.$symbol.', "'.$methodName.'", '.$cachePointer.$paramsStr.');'); + $compilationContext->codePrinter->output('ZEPHIR_CALL_'.$context.'('.$symbol.', "'.$methodName.'", '.$cachePointer.$paramsStr.');'); } } else { - $codePrinter->output('ZEPHIR_CALL_'.$context.'(NULL, "'.$methodName.'", '.$cachePointer.$paramsStr.');'); + $compilationContext->codePrinter->output('ZEPHIR_CALL_'.$context.'(NULL, "'.$methodName.'", '.$cachePointer.$paramsStr.');'); } } else { $ce = $method->getClassDefinition()->getClassEntry($compilationContext); if ($isExpecting) { - if ('return_value' == $symbolVariable->getName()) { + if ('return_value' === $symbolVariable->getName()) { $macro = $compilationContext->backend->getFcallManager()->getMacro(true, true, $paramCount); - $codePrinter->output($macro.'('.$ce.', '.$method->getInternalName().$paramsStr.');'); + $compilationContext->codePrinter->output($macro.'('.$ce.', '.$method->getInternalName().$paramsStr.');'); } else { $macro = $compilationContext->backend->getFcallManager()->getMacro(true, 2, $paramCount); - $codePrinter->output($macro.'('.$symbol.', '.$ce.', '.$method->getInternalName().$paramsStr.');'); + $compilationContext->codePrinter->output($macro.'('.$symbol.', '.$ce.', '.$method->getInternalName().$paramsStr.');'); } } else { $macro = $compilationContext->backend->getFcallManager()->getMacro(true, false, $paramCount); - $codePrinter->output($macro.'('.$ce.', '.$method->getInternalName().$paramsStr.');'); + $compilationContext->codePrinter->output($macro.'('.$ce.', '.$method->getInternalName().$paramsStr.');'); } } - /* + /** * Temporary variables must be copied if they have more than one reference */ foreach ($this->getMustCheckForCopyVariables() as $checkVariable) { - $codePrinter->output('zephir_check_temp_parameter('.$checkVariable.');'); + $compilationContext->codePrinter->output('zephir_check_temp_parameter('.$checkVariable.');'); } $this->addCallStatusOrJump($compilationContext); @@ -363,22 +336,30 @@ protected function call( /** * Calls static methods on the 'parent' context. * - * @param string $methodName - * @param array $expression - * @param Variable $symbolVariable - * @param bool $mustInit - * @param bool $isExpecting - * @param ClassDefinition $classDefinition + * @param string $methodName + * @param array $expression + * @param Variable|null $symbolVariable + * @param bool $mustInit + * @param bool $isExpecting + * @param ClassDefinition $classDefinition * @param CompilationContext $compilationContext - * @param ClassMethod $method + * @param ClassMethod $method + * @throws Exception */ - protected function callParent($methodName, array $expression, $symbolVariable, $mustInit, $isExpecting, ClassDefinition $classDefinition, CompilationContext $compilationContext, ClassMethod $method) - { + protected function callParent( + string $methodName, + array $expression, + ?Variable $symbolVariable, + bool $mustInit, + bool $isExpecting, + ClassDefinition $classDefinition, + CompilationContext $compilationContext, + ClassMethod $method + ): void { $codePrinter = $compilationContext->codePrinter; $classCe = $classDefinition->getClassEntry($compilationContext); - //$className = str_replace('\\', '\\\\', $classDefinition->getCompleteName()); - /* + /** * Call static methods must grown the stack */ $compilationContext->symbolTable->mustGrownStack(true); @@ -392,15 +373,10 @@ protected function callParent($methodName, array $expression, $symbolVariable, $ * Check if the method call can have an inline cache. */ $methodCache = $compilationContext->cacheManager->getStaticMethodCache(); - $cachePointer = $methodCache->get($compilationContext, isset($method) ? $method : null); + $cachePointer = $methodCache->get($compilationContext, $method ?? null); - if (isset($expression['parameters']) && \count($expression['parameters'])) { - $params = $this->getResolvedParams($expression['parameters'], $compilationContext, $expression); - } else { - $params = []; - } - - if (!\count($params)) { + $params = $this->getResolvedParameters($expression, $compilationContext); + if (!count($params)) { if ($isExpecting) { if ('return_value' == $symbolVariable->getName()) { $codePrinter->output('ZEPHIR_RETURN_CALL_PARENT('.$classCe.', getThis(), "'.$methodName.'", '.$cachePointer.');'); @@ -422,7 +398,7 @@ protected function callParent($methodName, array $expression, $symbolVariable, $ } } - /* + /** * Temporary variables must be copied if they have more than one reference */ foreach ($this->getMustCheckForCopyVariables() as $checkVariable) { @@ -435,33 +411,37 @@ protected function callParent($methodName, array $expression, $symbolVariable, $ /** * Calls static methods on the some class context. * - * @param string $methodName - * @param array $expression - * @param Variable $symbolVariable - * @param bool $mustInit - * @param bool $isExpecting - * @param ClassDefinition $classDefinition + * @param string $methodName + * @param array $expression + * @param Variable|null $symbolVariable + * @param bool $mustInit + * @param bool $isExpecting + * @param ClassDefinition $classDefinition * @param CompilationContext $compilationContext - * @param ClassMethod $method + * @param ClassMethod $method + * @throws Exception */ - protected function callFromClass($methodName, array $expression, $symbolVariable, $mustInit, $isExpecting, ClassDefinition $classDefinition, CompilationContext $compilationContext, ClassMethod $method) - { + protected function callFromClass( + string $methodName, + array $expression, + ?Variable $symbolVariable, + bool $mustInit, + bool $isExpecting, + ClassDefinition $classDefinition, + CompilationContext $compilationContext, + ClassMethod $method + ): void { $codePrinter = $compilationContext->codePrinter; if ($classDefinition->isBundled()) { - //if (!$compilationContext->symbolTable->hasVariable($variableName)) { - $classEntryVariable = $compilationContext->symbolTable->addTemp('zend_class_entry', $compilationContext); $compilationContext->backend->fetchClass($classEntryVariable, 'SL("'.str_replace('\\', '\\\\', $classDefinition->getName()).'")', false, $compilationContext); - //} - - //$classEntryVariable = $compilationContext->symbolTable->getVariableForWrite($variableName, $compilationContext, $expression); $classEntry = $classEntryVariable->getName(); } else { $classEntry = $classDefinition->getClassEntry($compilationContext); } - /* + /** * Call static methods must grown the stack */ $compilationContext->symbolTable->mustGrownStack(true); @@ -479,19 +459,14 @@ protected function callFromClass($methodName, array $expression, $symbolVariable * Check if the method call can have an inline cache. */ $methodCache = $compilationContext->cacheManager->getStaticMethodCache(); - $cachePointer = $methodCache->get($compilationContext, isset($method) ? $method : null); - - if (isset($expression['parameters']) && \count($expression['parameters'])) { - $params = $this->getResolvedParams($expression['parameters'], $compilationContext, $expression); - } else { - $params = []; - } + $cachePointer = $methodCache->get($compilationContext, $method ?? null); if ($symbolVariable) { $symbol = $compilationContext->backend->getVariableCodePointer($symbolVariable); } - $paramCount = \count($params); + $params = $this->getResolvedParameters($expression, $compilationContext); + $paramCount = count($params); $paramsStr = $paramCount ? ', '.implode(', ', $params) : ''; if ($method->isInternal()) { @@ -510,7 +485,7 @@ protected function callFromClass($methodName, array $expression, $symbolVariable } } else { if ($isExpecting) { - if ('return_value' == $symbolVariable->getName()) { + if ('return_value' === $symbolVariable->getName()) { $codePrinter->output('ZEPHIR_RETURN_CALL_CE_STATIC('.$classEntry.', "'.$methodName.'", '.$cachePointer.$paramsStr.');'); } else { $codePrinter->output('ZEPHIR_CALL_CE_STATIC('.$symbol.', '.$classEntry.', "'.$methodName.'", '.$cachePointer.$paramsStr.');'); @@ -520,7 +495,7 @@ protected function callFromClass($methodName, array $expression, $symbolVariable } } - /* + /** * Temporary variables must be copied if they have more than one reference */ foreach ($this->getMustCheckForCopyVariables() as $checkVariable) { @@ -535,16 +510,22 @@ protected function callFromClass($methodName, array $expression, $symbolVariable * * @param string $methodName * @param array $expression - * @param Variable $symbolVariable + * @param Variable|null $symbolVariable * @param bool $mustInit * @param bool $isExpecting * @param CompilationContext $compilationContext */ - protected function callFromDynamicClass($methodName, array $expression, $symbolVariable, $mustInit, $isExpecting, CompilationContext $compilationContext) - { + protected function callFromDynamicClass( + string $methodName, + array $expression, + ?Variable $symbolVariable, + bool $mustInit, + bool $isExpecting, + CompilationContext $compilationContext + ): void { $codePrinter = $compilationContext->codePrinter; - /* + /** * Call static methods must grown the stack */ $compilationContext->symbolTable->mustGrownStack(true); @@ -556,12 +537,6 @@ protected function callFromDynamicClass($methodName, array $expression, $symbolV $cachePointer = 'NULL, 0'; - if (isset($expression['parameters']) && \count($expression['parameters'])) { - $params = $this->getResolvedParams($expression['parameters'], $compilationContext, $expression); - } else { - $params = []; - } - /** * Obtain the class entry from the variable. */ @@ -586,7 +561,8 @@ protected function callFromDynamicClass($methodName, array $expression, $symbolV $symbol = $compilationContext->backend->getVariableCodePointer($symbolVariable); } - if (!\count($params)) { + $params = $this->getResolvedParameters($expression, $compilationContext); + if (!count($params)) { if ($isExpecting) { if ('return_value' == $symbolVariable->getName()) { $codePrinter->output('ZEPHIR_RETURN_CALL_CE_STATIC('.$classEntry.', "'.$methodName.'", '.$cachePointer.');'); @@ -608,7 +584,7 @@ protected function callFromDynamicClass($methodName, array $expression, $symbolV } } - /* + /** * Temporary variables must be copied if they have more than one reference */ foreach ($this->getMustCheckForCopyVariables() as $checkVariable) { @@ -622,16 +598,19 @@ protected function callFromDynamicClass($methodName, array $expression, $symbolV * Calls static methods on using a dynamic variable as class and a dynamic method. * * @param array $expression - * @param Variable $symbolVariable + * @param Variable|null $symbolVariable * @param bool $mustInit * @param bool $isExpecting * @param CompilationContext $compilationContext */ - protected function callFromDynamicClassDynamicMethod(array $expression, $symbolVariable, $mustInit, $isExpecting, CompilationContext $compilationContext) - { - $codePrinter = $compilationContext->codePrinter; - - /* + protected function callFromDynamicClassDynamicMethod( + array $expression, + ?Variable $symbolVariable, + bool $mustInit, + bool $isExpecting, + CompilationContext $compilationContext + ): void { + /** * Call static methods must grown the stack */ $compilationContext->symbolTable->mustGrownStack(true); @@ -643,12 +622,6 @@ protected function callFromDynamicClassDynamicMethod(array $expression, $symbolV $cachePointer = 'NULL, 0'; - if (isset($expression['parameters']) && \count($expression['parameters'])) { - $params = $this->getResolvedParams($expression['parameters'], $compilationContext, $expression); - } else { - $params = []; - } - /** * Obtain the class entry from the variable. */ @@ -657,11 +630,9 @@ protected function callFromDynamicClassDynamicMethod(array $expression, $symbolV throw new CompilerException('Only dynamic/string variables can be used in dynamic classes', $expression); } - $compilationContext->headersManager->add('kernel/object'); - $classEntryVariable = $compilationContext->symbolTable->addTemp('zend_class_entry', $compilationContext); - - $codePrinter->output( + $compilationContext->headersManager->add('kernel/object'); + $compilationContext->codePrinter->output( sprintf( '%s = zephir_fetch_class(%s);', $classEntryVariable->getName(), @@ -683,35 +654,50 @@ protected function callFromDynamicClassDynamicMethod(array $expression, $symbolV $symbol = $compilationContext->backend->getVariableCodePointer($symbolVariable); } - if (!\count($params)) { + $params = $this->getResolvedParameters($expression, $compilationContext); + if (!count($params)) { if ($isExpecting) { - if ('return_value' == $symbolVariable->getName()) { - $codePrinter->output('ZEPHIR_RETURN_CALL_CE_STATIC_ZVAL('.$classEntry.', '.$methodNameVariable->getName().', '.$cachePointer.');'); + if ('return_value' === $symbolVariable->getName()) { + $compilationContext->codePrinter->output('ZEPHIR_RETURN_CALL_CE_STATIC_ZVAL('.$classEntry.', '.$methodNameVariable->getName().', '.$cachePointer.');'); } else { - $codePrinter->output('ZEPHIR_CALL_CE_STATIC_ZVAL('.$symbol.', '.$classEntry.', '.$methodNameVariable->getName().', '.$cachePointer.');'); + $compilationContext->codePrinter->output('ZEPHIR_CALL_CE_STATIC_ZVAL('.$symbol.', '.$classEntry.', '.$methodNameVariable->getName().', '.$cachePointer.');'); } } else { - $codePrinter->output('ZEPHIR_CALL_CE_STATIC_ZVAL(NULL, '.$classEntry.', '.$methodNameVariable->getName().', '.$cachePointer.');'); + $compilationContext->codePrinter->output('ZEPHIR_CALL_CE_STATIC_ZVAL(NULL, '.$classEntry.', '.$methodNameVariable->getName().', '.$cachePointer.');'); } } else { if ($isExpecting) { - if ('return_value' == $symbolVariable->getName()) { - $codePrinter->output('ZEPHIR_RETURN_CALL_CE_STATIC_ZVAL('.$classEntry.', '.$methodNameVariable->getName().', '.$cachePointer.', '.implode(', ', $params).');'); + if ('return_value' === $symbolVariable->getName()) { + $compilationContext->codePrinter->output('ZEPHIR_RETURN_CALL_CE_STATIC_ZVAL('.$classEntry.', '.$methodNameVariable->getName().', '.$cachePointer.', '.implode(', ', $params).');'); } else { - $codePrinter->output('ZEPHIR_CALL_CE_STATIC_ZVAL('.$symbol.', '.$classEntry.', '.$methodNameVariable->getName().', '.$cachePointer.', '.implode(', ', $params).');'); + $compilationContext->codePrinter->output('ZEPHIR_CALL_CE_STATIC_ZVAL('.$symbol.', '.$classEntry.', '.$methodNameVariable->getName().', '.$cachePointer.', '.implode(', ', $params).');'); } } else { - $codePrinter->output('ZEPHIR_CALL_CE_STATIC_ZVAL(NULL, '.$classEntry.', '.$methodNameVariable->getName().', '.$cachePointer.', '.implode(', ', $params).');'); + $compilationContext->codePrinter->output('ZEPHIR_CALL_CE_STATIC_ZVAL(NULL, '.$classEntry.', '.$methodNameVariable->getName().', '.$cachePointer.', '.implode(', ', $params).');'); } } - /* + /** * Temporary variables must be copied if they have more than one reference */ foreach ($this->getMustCheckForCopyVariables() as $checkVariable) { - $codePrinter->output('zephir_check_temp_parameter('.$checkVariable.');'); + $compilationContext->codePrinter->output('zephir_check_temp_parameter('.$checkVariable.');'); } $this->addCallStatusOrJump($compilationContext); } + + /** + * @param array $expression + * @param CompilationContext $compilationContext + * @return array + */ + private function getResolvedParameters(array $expression, CompilationContext $compilationContext): array + { + if (empty($expression['parameters'])) { + return []; + } + + return $this->getResolvedParams($expression['parameters'], $compilationContext, $expression); + } } diff --git a/Library/SymbolTable.php b/Library/SymbolTable.php index b451929cf0..590c714999 100644 --- a/Library/SymbolTable.php +++ b/Library/SymbolTable.php @@ -9,12 +9,18 @@ * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Zephir; use Zephir\Exception\CompilerException; use Zephir\Passes\LocalContextPass; use Zephir\Variable\Globals; +use function count; +use function is_object; +use function strlen; + /** * Zephir\SymbolTable. * @@ -22,43 +28,64 @@ */ class SymbolTable { - protected $mustGrownStack = false; + /** + * Must grow memory stack + * + * @var bool + */ + protected bool $mustGrownStack = false; - /** @var Variable[][] */ - protected $branchVariables = []; + /** + * Branch of variables + * + * @var Variable[][] + */ + protected array $branchVariables = []; - protected $tempVarCount = 0; + /** + * Temporary variables counter + * + * @var int + */ + protected int $tempVarCount = 0; - /** @var Variable[][][][] */ - protected $branchTempVariables = []; + /** + * Branch of temporary variables + * + * @var Variable[][][][] + */ + protected array $branchTempVariables = []; /** - * @var LocalContextPass + * @var LocalContextPass|null */ - protected $localContext; + protected ?LocalContextPass $localContext = null; /** * @var CompilationContext */ - protected $compilationContext; + protected CompilationContext $compilationContext; /** * @var Globals */ - protected $globalsManager; + protected Globals $globalsManager; /** - * SymbolTable. + * Constructor * * @param CompilationContext $compilationContext */ public function __construct(CompilationContext $compilationContext) { $this->globalsManager = new Globals(); - - /* The variables are registered in branch 1, which is the external branch */ $this->compilationContext = $compilationContext; + + /** + * The variables are registered in branch 1, which is the external branch + */ $this->branchVariables[1] = []; + /* this_ptr */ $thisVar = new Variable('variable', 'this', $compilationContext->branchManager->getCurrentBranch()); $thisVar->setIsInitialized(true, $compilationContext); @@ -79,7 +106,14 @@ public function __construct(CompilationContext $compilationContext) $this->branchVariables[1]['return_value_ptr'] = $returnValue; } - public function resolveVariableToBranch($name, CompilationContext $compilationContext) + /** + * Find branch of Variable Name + * + * @param string $name + * @param CompilationContext $compilationContext + * @return Branch|null + */ + public function resolveVariableToBranch(string $name, CompilationContext $compilationContext): ?Branch { $currentBranch = $compilationContext->branchManager->getCurrentBranch(); @@ -89,7 +123,7 @@ public function resolveVariableToBranch($name, CompilationContext $compilationCo return $currentBranch; } $currentBranch = $currentBranch->getParentBranch(); - } while (null != $currentBranch); + } while (null !== $currentBranch); return null; } @@ -99,7 +133,7 @@ public function resolveVariableToBranch($name, CompilationContext $compilationCo * * @param LocalContextPass $localContext */ - public function setLocalContext(LocalContextPass $localContext) + public function setLocalContext(LocalContextPass $localContext): void { $this->localContext = $localContext; } @@ -107,17 +141,25 @@ public function setLocalContext(LocalContextPass $localContext) /** * Check if a variable is declared in the current symbol table. * - * @param string $name - * @param CompilationContext $compilationContext + * @param string $name + * @param CompilationContext|null $compilationContext * * @return bool */ - public function hasVariable($name, CompilationContext $compilationContext = null) + public function hasVariable(string $name, CompilationContext $compilationContext = null): bool { return false !== $this->getVariable($name, $compilationContext ?: $this->compilationContext); } - public function hasVariableInBranch($name, Branch $compareBranch, CompilationContext $compilationContext = null) + /** + * Check if variable is declared in the Branch + * + * @param string $name + * @param Branch $compareBranch + * @param CompilationContext|null $compilationContext + * @return bool + */ + public function hasVariableInBranch(string $name, Branch $compareBranch, CompilationContext $compilationContext = null): bool { $branch = $this->resolveVariableToBranch($name, $compilationContext ?: $this->compilationContext); @@ -140,7 +182,7 @@ public function addVariable( ): Variable { $currentBranch = $compilationContext->branchManager->getCurrentBranch(); $branchId = $currentBranch->getUniqueId(); - if ($this->globalsManager->isSuperGlobal($name) || 'zephir_fcall_cache_entry' == $type) { + if ($this->globalsManager->isSuperGlobal($name) || 'zephir_fcall_cache_entry' === $type) { $branchId = 1; } @@ -151,10 +193,10 @@ public function addVariable( $variable = new Variable($type, $varName, $currentBranch); - /* + /** * Checks whether a variable can be optimized to be static or not */ - if ('variable' == $type && $this->localContext && $this->localContext->shouldBeLocal($name)) { + if ('variable' === $type && $this->localContext && $this->localContext->shouldBeLocal($name)) { $variable->setLocalOnly(true); } @@ -174,11 +216,12 @@ public function addVariable( * * @return Variable */ - public function addRawVariable(Variable $variable) + public function addRawVariable(Variable $variable): Variable { if (!isset($this->branchVariables[1])) { $this->branchVariables[1] = []; } + $this->branchVariables[1][$variable->getName()] = $variable; return $variable; @@ -187,17 +230,17 @@ public function addRawVariable(Variable $variable) /** * Returns a variable in the symbol table. * - * @param $name - * @param CompilationContext $compilationContext + * @param string $name + * @param CompilationContext|null $compilationContext * - * @return bool|Variable + * @return bool|Variable TODO: Change to ?Variable */ - public function getVariable($name, CompilationContext $compilationContext = null) + public function getVariable(string $name, CompilationContext $compilationContext = null) { /* Check if the variable already is referencing a branch */ $pos = strpos($name, Variable::BRANCH_MAGIC); if ($pos > -1) { - $branchId = (int) (substr($name, $pos + \strlen(Variable::BRANCH_MAGIC))); + $branchId = (int) (substr($name, $pos + strlen(Variable::BRANCH_MAGIC))); $name = substr($name, 0, $pos); } else { $compilationContext = $compilationContext ?: $this->compilationContext; @@ -205,8 +248,10 @@ public function getVariable($name, CompilationContext $compilationContext = null if (!$branch) { return false; } + $branchId = $branch->getUniqueId(); } + if (!isset($this->branchVariables[$branchId]) || !isset($this->branchVariables[$branchId][$name])) { return false; } @@ -219,50 +264,40 @@ public function getVariable($name, CompilationContext $compilationContext = null * * @return Variable[] */ - public function getVariables() + public function getVariables(): array { - $ret = []; - foreach ($this->branchVariables as $branchId => $vars) { + $variables = []; + + foreach ($this->branchVariables as $vars) { foreach ($vars as $var) { - $ret[$var->getName()] = $var; + $variables[$var->getName()] = $var; } } - return $ret; - } - - public function getVariablesByBranch($branchId) - { - if (isset($this->branchVariables[$branchId])) { - return $this->branchVariables[$branchId]; - } - - return null; + return $variables; } /** * Return a variable in the symbol table, it will be used for a read operation. * - * @param string $name - * @param CompilationContext $compilationContext - * @param array $statement - * - * @throws CompilerException + * @param string $name + * @param CompilationContext|null $compilationContext + * @param array|null $statement * * @return Variable */ - public function getVariableForRead($name, CompilationContext $compilationContext = null, array $statement = null) + public function getVariableForRead(string $name, CompilationContext $compilationContext = null, array $statement = null): Variable { - /* + /** * Validate that 'this' cannot be used in a static function */ - if ('this' == $name || 'this_ptr' == $name) { + if ('this' === $name || 'this_ptr' === $name) { if ($compilationContext->staticContext) { throw new CompilerException("Cannot use variable 'this' in static context", $statement); } } - /* + /** * Create superglobals just in time */ if ($this->globalsManager->isSuperGlobal($name)) { @@ -278,6 +313,7 @@ public function getVariableForRead($name, CompilationContext $compilationContext } else { $variable = $this->getVariable($name); } + $variable->increaseUses(); return $variable; @@ -289,18 +325,17 @@ public function getVariableForRead($name, CompilationContext $compilationContext $variable = $this->getVariable($name); if (!$variable->isInitialized()) { - /* DON'T REMOVE THIS!! */ throw new CompilerException("Variable '".$name."' cannot be read because it's not initialized", $statement); } $variable->increaseUses(); - /* + /** * Analise branches to detect possible initialization of variables in conditional branches */ if (!$variable->isTemporal() && !$variable->getSkipVariant()) { - if ('return_value' != $name && 'this' != $name) { - if (\is_object($compilationContext) && \is_object($compilationContext->branchManager)) { + if ('return_value' !== $name && 'this' !== $name) { + if ($compilationContext instanceof CompilationContext && is_object($compilationContext->branchManager)) { if ($compilationContext->config->get('check-invalid-reads', 'optimizations')) { switch ($variable->getType()) { case 'variable': @@ -316,16 +351,15 @@ public function getVariableForRead($name, CompilationContext $compilationContext $initBranches = $variable->getInitBranches(); $currentBranch = $compilationContext->branchManager->getCurrentBranch(); - /** @var Branch[] $branches */ $branches = array_reverse($initBranches); - if (1 == \count($branches)) { - if (Branch::TYPE_CONDITIONAL_TRUE == $branches[0]->getType()) { + if (1 === count($branches)) { + if (Branch::TYPE_CONDITIONAL_TRUE === $branches[0]->getType()) { if (true === $branches[0]->isUnreachable()) { throw new CompilerException('Initialization of variable "'.$name.'" depends on unreachable branch, consider initialize it at its declaration', $statement); } } else { - if (Branch::TYPE_CONDITIONAL_FALSE == $branches[0]->getType()) { + if (Branch::TYPE_CONDITIONAL_FALSE === $branches[0]->getType()) { if (false === $branches[0]->isUnreachable()) { throw new CompilerException('Initialization of variable "'.$name.'" depends on unreachable branch, consider initialize at its declaration', $statement); } @@ -334,7 +368,7 @@ public function getVariableForRead($name, CompilationContext $compilationContext $tempBranch = $branches[0]->getParentBranch(); while ($tempBranch) { - if (Branch::TYPE_CONDITIONAL_TRUE == $tempBranch->getType()) { + if (Branch::TYPE_CONDITIONAL_TRUE === $tempBranch->getType()) { if (true === $tempBranch->isUnreachable()) { throw new CompilerException('Initialization of variable "'.$name.'" depends on unreachable branch, consider initialize it at its declaration', $statement); } @@ -346,7 +380,7 @@ public function getVariableForRead($name, CompilationContext $compilationContext $found = false; foreach ($branches as $branch) { - /* + /** * Variable was initialized in the same current branch */ if ($branch === $currentBranch) { @@ -354,7 +388,7 @@ public function getVariableForRead($name, CompilationContext $compilationContext break; } - /* + /** * 'root' and 'external' branches are safe branches */ if (Branch::TYPE_ROOT == $branch->getType() || Branch::TYPE_EXTERNAL == $branch->getType()) { @@ -375,14 +409,13 @@ public function getVariableForRead($name, CompilationContext $compilationContext } if ($possibleBadAssignment) { - if (\count($branches) > 1) { + if (count($branches) > 1) { $graph = new BranchGraph(); foreach ($branches as $branch) { $graph->addLeaf($branch); } - //echo $graph->getRoot()->show(); } else { - /* + /** * Variable is assigned just once and it's assigned in a conditional branch */ if (Branch::TYPE_CONDITIONAL_TRUE == $branches[0]->getType()) { @@ -419,7 +452,7 @@ public function getVariableForRead($name, CompilationContext $compilationContext } } - /* + /** * Saves the latest place where the variable was used */ $variable->setUsed(true, $statement); @@ -431,17 +464,15 @@ public function getVariableForRead($name, CompilationContext $compilationContext * Return a variable in the symbol table, it will be used for a write operation * Some variables aren't writable themselves but their members do. * - * @param string $name + * @param string $name * @param CompilationContext $compilationContext - * @param array $statement - * - * @throws CompilerException + * @param array|null $statement * - * @return bool|\Zephir\Variable + * @return bool|Variable TODO: Change to ?Variable */ - public function getVariableForWrite($name, CompilationContext $compilationContext, array $statement = null) + public function getVariableForWrite(string $name, CompilationContext $compilationContext, array $statement = null) { - /* + /** * Create superglobals just in time */ if ($this->globalsManager->isSuperGlobal($name)) { @@ -470,7 +501,7 @@ public function getVariableForWrite($name, CompilationContext $compilationContex $variable->increaseUses(); $variable->increaseMutates(); - /* + /** * Saves the last place where the variable was mutated * We discard mutations inside loops because iterations could use the value * and Zephir only provides top-down compilation @@ -488,35 +519,31 @@ public function getVariableForWrite($name, CompilationContext $compilationContex * Return a variable in the symbol table, it will be used for a mutating operation * This method implies mutation of one of the members of the variable but no the variables it self. * - * @param string $name + * @param string $name * @param CompilationContext $compilationContext - * @param array $statement - * - * @throws CompilerException + * @param array|null $statement * * @return Variable */ - public function getVariableForUpdate($name, CompilationContext $compilationContext, array $statement = null) + public function getVariableForUpdate(string $name, CompilationContext $compilationContext, array $statement = null): Variable { - /* + /** * Create superglobals just in time */ - if ($this->globalsManager->isSuperGlobal($name)) { - if (!$this->hasVariable($name)) { - /** - * TODO:, injecting globals, initialize to null and check first? - */ - $superVar = new Variable('variable', $name, $compilationContext->branchManager->getCurrentBranch()); - $superVar->setIsInitialized(true, $compilationContext); - $superVar->setDynamicTypes('array'); - $superVar->increaseMutates(); - $superVar->increaseUses(); - $superVar->setIsExternal(true); - $superVar->setUsed(true, $statement); - $this->addRawVariable($superVar); - - return $superVar; - } + if ($this->globalsManager->isSuperGlobal($name) && !$this->hasVariable($name)) { + /** + * TODO:, injecting globals, initialize to null and check first? + */ + $superVar = new Variable('variable', $name, $compilationContext->branchManager->getCurrentBranch()); + $superVar->setIsInitialized(true, $compilationContext); + $superVar->setDynamicTypes('array'); + $superVar->increaseMutates(); + $superVar->increaseUses(); + $superVar->setIsExternal(true); + $superVar->setUsed(true, $statement); + $this->addRawVariable($superVar); + + return $superVar; } if (!$this->hasVariable($name)) { @@ -527,7 +554,7 @@ public function getVariableForUpdate($name, CompilationContext $compilationConte $variable->increaseUses(); $variable->increaseMutates(); - /* + /** * Saves the last place where the variable was mutated * We discard mutations inside loops because iterations could use the value * and Zephir only provides top-down compilation @@ -542,7 +569,7 @@ public function getVariableForUpdate($name, CompilationContext $compilationConte * * @param bool $mustGrownStack */ - public function mustGrownStack($mustGrownStack) + public function mustGrownStack(bool $mustGrownStack): void { $this->mustGrownStack = $mustGrownStack; } @@ -552,7 +579,7 @@ public function mustGrownStack($mustGrownStack) * * @return bool */ - public function getMustGrownStack() + public function getMustGrownStack(): bool { return $this->mustGrownStack; } @@ -560,7 +587,7 @@ public function getMustGrownStack() /** * @return int */ - public function getNextTempVar() + public function getNextTempVar(): int { return $this->tempVarCount++; } @@ -591,12 +618,13 @@ public function getTempVariable(string $type, CompilationContext $compilationCon * * @return Variable */ - public function getTempVariableForWrite($type, CompilationContext $context, $init = true) + public function getTempVariableForWrite(string $type, CompilationContext $context, $init = true): Variable { $variable = $this->reuseTempVariable($type, 'heap'); - if (\is_object($variable)) { + if ($variable instanceof Variable) { $variable->increaseUses(); $variable->increaseMutates(); + if ($init && ('variable' == $type || 'string' == $type || 'array' == $type)) { $variable->initVariant($context); } @@ -629,11 +657,11 @@ public function getTempVariableForWrite($type, CompilationContext $context, $ini * * @return Variable */ - public function getTempNonTrackedVariable($type, CompilationContext $context, $initNonReferenced = false) + public function getTempNonTrackedVariable($type, CompilationContext $context, bool $initNonReferenced = false): Variable { $variable = $this->reuseTempVariable($type, 'non-tracked'); - if (\is_object($variable)) { + if (is_object($variable)) { $variable->increaseUses(); $variable->increaseMutates(); if ($initNonReferenced) { @@ -669,10 +697,10 @@ public function getTempNonTrackedVariable($type, CompilationContext $context, $i * * @return Variable */ - public function getTempNonTrackedUninitializedVariable($type, CompilationContext $context) + public function getTempNonTrackedUninitializedVariable(string $type, CompilationContext $context): Variable { $variable = $this->reuseTempVariable($type, 'non-tracked-uninitialized'); - if (\is_object($variable)) { + if (is_object($variable)) { $variable->increaseUses(); $variable->increaseMutates(); @@ -702,12 +730,13 @@ public function getTempNonTrackedUninitializedVariable($type, CompilationContext * * @return Variable */ - public function getTempComplexLiteralVariableForWrite($type, CompilationContext $context) + public function getTempComplexLiteralVariableForWrite(string $type, CompilationContext $context): Variable { $variable = $this->reuseTempVariable($type, 'heap-literal'); - if (\is_object($variable)) { + if ($variable instanceof Variable) { $variable->increaseUses(); $variable->increaseMutates(); + if ('variable' == $type || 'string' == $type || 'array' == $type) { $variable->initComplexLiteralVariant($context); } @@ -715,12 +744,12 @@ public function getTempComplexLiteralVariableForWrite($type, CompilationContext return $variable; } - $tempVar = $this->getNextTempVar(); - $variable = $this->addVariable($type, '_'.$tempVar, $context); + $variable = $this->addVariable($type, '_'.$this->getNextTempVar(), $context); $variable->setIsInitialized(true, $context); $variable->increaseUses(); $variable->increaseMutates(); $variable->setTemporal(true); + if ('variable' == $type || 'string' == $type || 'array' == $type) { $variable->initComplexLiteralVariant($context); } @@ -738,13 +767,14 @@ public function getTempComplexLiteralVariableForWrite($type, CompilationContext * * @return Variable */ - public function getTempLocalVariableForWrite($type, CompilationContext $context) + public function getTempLocalVariableForWrite(string $type, CompilationContext $context): Variable { $variable = $this->reuseTempVariable($type, 'stack'); - if (\is_object($variable)) { + if ($variable instanceof Variable) { $variable->increaseUses(); $variable->increaseMutates(); $variable->setLocalOnly(true); + if ('variable' == $type || 'string' == $type || 'array' == $type) { $variable->initVariant($context); } @@ -752,13 +782,13 @@ public function getTempLocalVariableForWrite($type, CompilationContext $context) return $variable; } - $tempVar = $this->getNextTempVar(); - $variable = $this->addVariable($type, '_'.$tempVar, $context); + $variable = $this->addVariable($type, '_'.$this->getNextTempVar(), $context); $variable->setIsInitialized(true, $context); $variable->increaseUses(); $variable->increaseMutates(); $variable->setLocalOnly(true); $variable->setTemporal(true); + if ('variable' == $type || 'string' == $type || 'array' == $type) { $variable->initVariant($context); } @@ -776,7 +806,7 @@ public function getTempLocalVariableForWrite($type, CompilationContext $context) * * @return Variable */ - public function addTemp($type, CompilationContext $context) + public function addTemp(string $type, CompilationContext $context): Variable { $tempVar = $this->getNextTempVar(); $variable = $this->addVariable($type, '_'.$tempVar, $context); @@ -797,10 +827,10 @@ public function addTemp($type, CompilationContext $context) * * @return Variable */ - public function getTempVariableForObserve($type, CompilationContext $context) + public function getTempVariableForObserve(string $type, CompilationContext $context): Variable { $variable = $this->reuseTempVariable($type, 'observe'); - if (\is_object($variable)) { + if ($variable instanceof Variable) { $variable->increaseUses(); $variable->increaseMutates(); $variable->observeVariant($context); @@ -830,10 +860,10 @@ public function getTempVariableForObserve($type, CompilationContext $context) * * @return Variable */ - public function getTempVariableForObserveOrNullify($type, CompilationContext $context) + public function getTempVariableForObserveOrNullify(string $type, CompilationContext $context): Variable { $variable = $this->reuseTempVariable($type, 'observe-nullify'); - if (\is_object($variable)) { + if ($variable instanceof Variable) { $variable->increaseUses(); $variable->increaseMutates(); $variable->observeOrNullifyVariant($context); @@ -860,7 +890,7 @@ public function getTempVariableForObserveOrNullify($type, CompilationContext $co * * @param CompilationContext $compilationContext */ - public function markTemporalVariablesIdle(CompilationContext $compilationContext) + public function markTemporalVariablesIdle(CompilationContext $compilationContext): void { $compilationContext = $compilationContext ?: $this->compilationContext; $branchId = $compilationContext->branchManager->getCurrentBranchId(); @@ -869,14 +899,15 @@ public function markTemporalVariablesIdle(CompilationContext $compilationContext return; } - foreach ($this->branchTempVariables[$branchId] as $location => $typeVariables) { - foreach ($typeVariables as $type => $variables) { + foreach ($this->branchTempVariables[$branchId] as $typeVariables) { + foreach ($typeVariables as $variables) { foreach ($variables as $variable) { $pos = strpos($variable->getName(), Variable::BRANCH_MAGIC); $otherBranchId = 1; if ($pos > -1) { - $otherBranchId = (int) (substr($variable->getName(), $pos + \strlen(Variable::BRANCH_MAGIC))); + $otherBranchId = (int) (substr($variable->getName(), $pos + strlen(Variable::BRANCH_MAGIC))); } + if ($branchId == $otherBranchId) { $variable->setIdle(true); } @@ -892,7 +923,7 @@ public function markTemporalVariablesIdle(CompilationContext $compilationContext * * @return int */ - public function getExpectedMutations($variable) + public function getExpectedMutations(string $variable): int { if ($this->localContext) { return $this->localContext->getNumberOfMutations($variable); @@ -908,7 +939,7 @@ public function getExpectedMutations($variable) * * @return int */ - public function getLastCallLine() + public function getLastCallLine(): int { if ($this->localContext) { return $this->localContext->getLastCallLine(); @@ -924,7 +955,7 @@ public function getLastCallLine() * * @return int */ - public function getLastUnsetLine() + public function getLastUnsetLine(): int { if ($this->localContext) { return $this->localContext->getLastUnsetLine(); @@ -936,12 +967,12 @@ public function getLastUnsetLine() /** * Register a variable as temporal. * - * @param string $type - * @param string $location - * @param Variable $variable - * @param CompilationContext $compilationContext + * @param string $type + * @param string $location + * @param Variable $variable + * @param CompilationContext|null $compilationContext */ - protected function registerTempVariable($type, $location, Variable $variable, CompilationContext $compilationContext = null) + protected function registerTempVariable(string $type, string $location, Variable $variable, ?CompilationContext $compilationContext = null): void { $compilationContext = $compilationContext ?: $this->compilationContext; $branchId = $compilationContext->branchManager->getCurrentBranchId(); @@ -949,31 +980,30 @@ protected function registerTempVariable($type, $location, Variable $variable, Co if (!isset($this->branchTempVariables[$branchId][$location][$type])) { $this->branchTempVariables[$branchId][$location][$type] = []; } + $this->branchTempVariables[$branchId][$location][$type][] = $variable; } /** * Reuse variables marked as idle after leave a branch. * - * @param string $type - * @param string $location - * @param CompilationContext $compilationContext + * @param string $type + * @param string $location + * @param CompilationContext|null $compilationContext * - * @return Variable + * @return Variable|null */ - protected function reuseTempVariable(string $type, $location, CompilationContext $compilationContext = null) + protected function reuseTempVariable(string $type, string $location, ?CompilationContext $compilationContext = null): ?Variable { $compilationContext = $compilationContext ?: $this->compilationContext; $branchId = $compilationContext->branchManager->getCurrentBranchId(); if (isset($this->branchTempVariables[$branchId][$location][$type])) { foreach ($this->branchTempVariables[$branchId][$location][$type] as $variable) { - if (!$variable->isDoublePointer()) { - if ($variable->isIdle()) { - $variable->setIdle(false); + if (!$variable->isDoublePointer() && $variable->isIdle()) { + $variable->setIdle(false); - return $variable; - } + return $variable; } } } diff --git a/Library/Variable.php b/Library/Variable.php index 56947d72bb..7fe6c27d7c 100644 --- a/Library/Variable.php +++ b/Library/Variable.php @@ -832,7 +832,7 @@ public function initVariant(CompilationContext $compilationContext) return; } - /* + /** * Variables are allocated for the first time using ZEPHIR_INIT_VAR * the second, third, etc times are allocated using ZEPHIR_INIT_NVAR * Variables initialized for the first time in a cycle are always initialized using ZEPHIR_INIT_NVAR From 92b79d9f1874b11427c9f5a0a830f57b1f0d09a9 Mon Sep 17 00:00:00 2001 From: Anton Vasiliev Date: Sat, 24 Apr 2021 19:26:02 +0100 Subject: [PATCH 3/3] #733 - Add test case --- stub/builtin/arraymethods.zep | 18 ++++++++ tests/Extension/BuiltIn/ArrayMethodsTest.php | 47 ++++++++++++++++++++ tests/Extension/BuiltIn/CharMethodTest.php | 4 +- 3 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 tests/Extension/BuiltIn/ArrayMethodsTest.php diff --git a/stub/builtin/arraymethods.zep b/stub/builtin/arraymethods.zep index e2daaa5fb1..e3e65fa5a1 100644 --- a/stub/builtin/arraymethods.zep +++ b/stub/builtin/arraymethods.zep @@ -17,4 +17,22 @@ class ArrayMethods { return [1, 2, 3]->map(x => x * 100); } + + /** + * @issue https://github.com/zephir-lang/zephir/issues/733 + */ + public function issue733BuiltInJoinSpecialChars(string! a, var b = null) -> string + { + if b === null || b === false { + return a->trimleft(); + } + + if typeof b == "array" { + let b = b->join(""); + } + + let b = preg_replace("#[-\[\]:\\\\^/]#", "\\\\$0", b); + + return preg_replace("/^[" . b . "]+/u", "", a); + } } diff --git a/tests/Extension/BuiltIn/ArrayMethodsTest.php b/tests/Extension/BuiltIn/ArrayMethodsTest.php new file mode 100644 index 0000000000..03edbd6c69 --- /dev/null +++ b/tests/Extension/BuiltIn/ArrayMethodsTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Extension\BuiltIn; + +use PHPUnit\Framework\TestCase; +use Stub\BuiltIn\ArrayMethods; + +final class ArrayMethodsTest extends TestCase +{ + private ?ArrayMethods $arrayMethods; + + public function setUp(): void + { + $this->arrayMethods = new ArrayMethods(); + } + + public function testGetJoin1(): void + { + $this->assertSame('1-2-3', $this->arrayMethods->getJoin1()); + } + + public function testGetReversed1(): void + { + $this->assertSame([3, 2, 1], $this->arrayMethods->getReversed1()); + } + + public function testGetMap1(): void + { + $this->assertSame([100, 200, 300], $this->arrayMethods->getMap1()); + } + + public function testIssue733BuiltInJoinSpecialChars(): void + { + $this->assertSame('có', $this->arrayMethods->issue733BuiltInJoinSpecialChars('ålcó', ['å', 'l'])); + } +} diff --git a/tests/Extension/BuiltIn/CharMethodTest.php b/tests/Extension/BuiltIn/CharMethodTest.php index ea44123a17..ab0941309e 100644 --- a/tests/Extension/BuiltIn/CharMethodTest.php +++ b/tests/Extension/BuiltIn/CharMethodTest.php @@ -1,7 +1,5 @@