From ab5dfaa72272609d3a1f677781bc636b8c6846a3 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Sat, 11 Jul 2026 16:01:42 +0800 Subject: [PATCH] refactor(parser): move class constant fetch and nullsafe access to separate traits - Extracted class constant fetch logic from CompilerBase to ClassConstantFetchTrait - Moved nullsafe property and method access implementation to NullsafeAccessTrait - Added ClosureGenerator methods for arrow functions and closures - Introduced isReturnStmtInLastLine helper method for closure generation - Updated CompilerBase to use trait composition instead of inline methods - Added NodeFinder import for arrow function variable detection --- src/CompilerBase.php | 276 +------------------------ src/Generator/ClosureGenerator.php | 46 +++++ src/Parser/ClassConstantFetchTrait.php | 99 +++++++++ src/Parser/NullsafeAccessTrait.php | 162 +++++++++++++++ 4 files changed, 311 insertions(+), 272 deletions(-) create mode 100644 src/Parser/ClassConstantFetchTrait.php create mode 100644 src/Parser/NullsafeAccessTrait.php diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 4ac1d069..bb2b37c1 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -37,8 +37,10 @@ use TypePhp\Optimizer\LoopVarOptimizer; use TypePhp\Parser\StdContainerTrait; use TypePhp\Parser\AssignOpTrait; use TypePhp\Parser\BinaryOpTrait; +use TypePhp\Parser\ClassConstantFetchTrait; use TypePhp\Parser\ExceptionControlFlowTrait; use TypePhp\Parser\FunctionCallTrait; +use TypePhp\Parser\NullsafeAccessTrait; use TypePhp\Parser\TypeConversionTrait; use TypePhp\Parser\TypeDetectionTrait; use TypePhp\Optimizer\FuncCallOptimizer; @@ -89,8 +91,10 @@ class CompilerBase implements PropertyAccessContext use MagicMethodDetector; use StdContainerTrait; use BinaryOpTrait; + use ClassConstantFetchTrait; use ExceptionControlFlowTrait; use FunctionCallTrait; + use NullsafeAccessTrait; use TypeConversionTrait; use TypeDetectionTrait; use AssignOpTrait; @@ -6533,88 +6537,6 @@ class CompilerBase implements PropertyAccessContext return $this->getLiteralString($this->getNamespacedClassName($name)); } - protected function parseClassConstFetch(Expr\ClassConstFetch $expr): string - { - if (!$this->isNameExpr($expr->class)) { - return $this->parseDynamicClassConstFetch($expr); - } - - $class = $this->parseIdentifier($expr->class); - $self = false; - if ($class === 'self' or $class === 'this_') { - // Trait 读取常量,必须动态获取类名 - if ($this->classDef->trait) { - $class = 'static'; - } else { - $self = true; - $class = $this->class; - } - } - - $const = $this->escapeString($this->parseIdentifier($expr->name)); - if ($class === 'static') { - if (!$this->methodDef) { - $this->fatalError($expr, "The 'static' keyword can only be used as the class name in class methods"); - } - if ($const === 'class') { - return Symbol::getCalledClass(); - } else { - return Symbol::constant() . '(' . Symbol::getCalledCe() . ', ' . $this->getLiteralString($const) . ')'; - } - } - - if ($self or $this->isNameExpr($expr->class)) { - $class = $this->getNamespacedClassName($class); - } - if ($const === 'class') { - if ($self or $this->isNameExpr($expr->class)) { - return $this->getLiteralString($class); - } - } - if (($self or $this->isNameExpr($expr->class)) and $this->isIdExpr($expr->name)) { - if ($this->hasClass($class)) { - $classDef = $this->getClass($class); - if ($classDef->enum) { - $ce = $this->getClassEntryPtr($class); - return 'php::getEnumCase(' . $ce . ', ' . $this->getLiteralString($const) . ')'; - } - $nativeConst = $this->findNativeClassConst($expr, $class, $const); - if ($nativeConst) { - return $nativeConst; - } - } - $ce = $this->getClassEntryPtr($class); - return Symbol::constant() . '(' . $ce . ', ' . $this->getLiteralString($const) . ')'; - } - $name = $class . '::' . $const; - $name = $this->getLiteralString($name); - return Symbol::constant() . '(' . $name . ')'; - } - - protected function parseDynamicClassConstFetch(Expr\ClassConstFetch $expr): string - { - $const = $this->escapeString($this->parseIdentifier($expr->name)); - $target = $this->materializeDynamicClassConstTarget($expr->class); - - if ($const === 'class') { - return 'php::fn::get_class(' . $target . ')'; - } - - $className = '(' . $target . '.isObject() ? php::fn::get_class(' . $target . ') : ' . $target . ')'; - return Symbol::constant() . '(php::concat({' . $className . ', "::", ' . $this->getLiteralString($const) . '}))'; - } - - protected function materializeDynamicClassConstTarget(NodeAbstract $expr): string - { - $this->assertExprCanBeUsedAsValue($expr, 'class constant target'); - [$value, $beforeStmts, $afterStmts] = $this->parseExprWithCapturedStmts($expr); - $tmpVar = $this->addTmpVar(self::TYPE_VAR); - $this->appendCapturedStmtLinesToContext($beforeStmts); - $this->context->beforeStmtLines[] = $tmpVar . ' = ' . $value . ';'; - $this->appendCapturedStmtLinesToContext($afterStmts); - return $tmpVar; - } - protected function parseShellExec(Expr\ShellExec $expr): string { $list = []; @@ -7443,196 +7365,6 @@ class CompilerBase implements PropertyAccessContext } } - protected function parseArrowFunction(Expr\ArrowFunction $expr): string - { - $nodeFinder = new NodeFinder(); - $vars = $nodeFinder->findInstanceOf($expr->expr, Variable::class); - $uses = []; - $params = []; - - foreach ($expr->params as $i => $param) { - if ($param->byRef) { - $this->fatalError($expr, 'Closure cannot use reference parameter'); - } - if ($param->var instanceof Variable) { - $params[$param->var->name] = $i; - } - } - - foreach ($vars as $var) { - $varName = $this->escapeVarName($this->parseVariable($var)); - if ($varName === 'this_' - or !$this->hasLocalVar($varName) - or isset($params[$var->name]) - or isset($uses[$varName])) { - continue; - } - $uses[$varName] = new Node\ClosureUse($var); - } - $uses = array_values($uses); - - return $this->genClosure($expr, $expr->params, $uses); - } - - protected function parseClosure(Expr\Closure $expr): string - { - return $this->genClosure($expr, $expr->params, $expr->uses); - } - - protected function isReturnStmtInLastLine(array $stmts): bool - { - if (count($stmts) === 0) { - return false; - } - return $stmts[array_key_last($stmts)] instanceof Node\Stmt\Return_; - } - - protected function parseNullsafePropertyFetch(Expr\NullsafePropertyFetch $expr): string - { - return $this->parseNullsafeExpr($expr); - } - - protected function parseNullsafePropertyFetchUpdate(Expr\NullsafePropertyFetch $expr): string - { - return $this->parseNodeWithUpdateAttribute( - $expr, - self::ATTR_PROPERTY_FETCH_UPDATE, - true, - fn() => $this->parseNullsafePropertyFetch($expr) - ); - } - - protected function parseNullsafeMethodCall(Expr\NullsafeMethodCall $expr): string - { - return $this->parseNullsafeExpr($expr); - } - - protected function parseNullsafeExpr( - Expr\PropertyFetch|Expr\MethodCall|Expr\NullsafePropertyFetch|Expr\NullsafeMethodCall $expr - ): string - { - $list = []; - $comment = $this->formatCppLineComment('Nullsafe Operator: ', $this->printer->prettyPrint([$expr])); - - while (1) { - if ($expr instanceof Expr\NullsafePropertyFetch) { - $list[] = ['property', $this->identifierToStr($expr->name, literal: true), $expr, true]; - $expr = $expr->var; - } elseif ($expr instanceof Expr\NullsafeMethodCall) { - $list[] = ['method', $this->identifierToStr($expr->name, literal: true), $expr->args, true]; - $expr = $expr->var; - } elseif ($expr instanceof Expr\PropertyFetch) { - $list[] = ['property', $this->identifierToStr($expr->name, literal: true), $expr, false]; - $expr = $expr->var; - } elseif ($expr instanceof Expr\MethodCall) { - $list[] = ['method', $this->identifierToStr($expr->name, literal: true), $expr->args, false]; - $expr = $expr->var; - } else { - if ($this->isVarExpr($expr)) { - $object = $this->parseIdentifier($expr); - if (!$this->hasVar($object)) { - $this->errorUndefinedVariable($expr); - } - $type = $this->getVarType($object); - if ($type === self::TYPE_OBJECT) { - break; - } - } - $object = $this->addTmpVar(self::TYPE_OBJECT); - $this->context->beforeStmtLines[] = $this->getIndent() . $object . ' = ' . $this->parseIdentifier($expr) . ';'; - break; - } - } - - $list = array_reverse($list); - $this->checkNullsafePropertyAccesses($expr, $list); - $last = array_key_last($list); - $tmpFn = $this->genTmpVarName(); - - $code = $comment . PHP_EOL . 'auto ' . $tmpFn . ' = [&]() -> ' . self::TYPE_VAR . '{' . PHP_EOL; - - foreach ($list as $key => $item) { - $tmpVar = $this->addTmpVar($key !== $last ? self::TYPE_OBJECT : self::TYPE_VAR); - if ($item[3]) { - $code .= "if ({$object}.isNull()) { return " . self::VALUE_NULL . '; }'; - } - if ($item[0] == 'property') { - $update = $this->escapeBool($this->isPropertyFetchUpdate($item[2])); - $code .= $this->getIndent() . "{$tmpVar} = {$object}.attr({$item[1]}, {$update});"; - } else { - $beforeStmtCount = count($this->context->beforeStmtLines); - $afterStmtCount = count($this->context->afterStmtLines); - $args = $this->parseCallArgs($item[2]); - $argBeforeStmts = array_slice($this->context->beforeStmtLines, $beforeStmtCount); - $argAfterStmts = array_slice($this->context->afterStmtLines, $afterStmtCount); - $this->context->beforeStmtLines = array_slice($this->context->beforeStmtLines, 0, $beforeStmtCount); - $this->context->afterStmtLines = array_slice($this->context->afterStmtLines, 0, $afterStmtCount); - if ($argBeforeStmts) { - $code .= $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $argBeforeStmts) . PHP_EOL; - } - $code .= $this->getIndent() . "{$tmpVar} = {$object}.call({$item[1]}, {$args});"; - if ($argAfterStmts) { - $code .= $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $argAfterStmts) . PHP_EOL; - } - } - $object = $tmpVar; - } - $code .= $this->getIndent() . "return {$object}; };"; - $this->context->beforeStmtLines[] = $code; - return "{$tmpFn}()"; - } - - private function containsNullsafeChain(NodeAbstract $expr): bool - { - while ($expr instanceof Expr\PropertyFetch - || $expr instanceof Expr\MethodCall - || $expr instanceof Expr\NullsafePropertyFetch - || $expr instanceof Expr\NullsafeMethodCall) { - if ($expr instanceof Expr\NullsafePropertyFetch || $expr instanceof Expr\NullsafeMethodCall) { - return true; - } - $expr = $expr->var; - } - - return false; - } - - private function checkNullsafePropertyAccesses(NodeAbstract $baseExpr, array $list): void - { - $properties = []; - foreach ($list as $item) { - if ($item[0] !== 'property') { - break; - } - - /** @var Expr\NullsafePropertyFetch $node */ - $node = $item[2]; - if (!$this->isIdExpr($node->name)) { - break; - } - - $properties[] = [ - 'node' => $node, - 'property' => $this->parseIdentifier($node->name), - ]; - } - - if (!$properties) { - return; - } - - $scope = $this->class ? $this->getFullClassName() : ''; - $results = $this->createPropertyAccessResolver()->resolveNullsafePropertyChain( - $this->detectClassOfExpr($baseExpr), - $properties, - $scope, - self::TYPE_OBJECT, - ); - foreach ($results as $index => $result) { - $this->applyNativePropertyAccessResult($properties[$index]['node'], $result); - } - } - protected function parseFullyQualifiedName(Node\Name\FullyQualified $expr): string { return $expr->name; diff --git a/src/Generator/ClosureGenerator.php b/src/Generator/ClosureGenerator.php index b58fcd34..049c52bd 100644 --- a/src/Generator/ClosureGenerator.php +++ b/src/Generator/ClosureGenerator.php @@ -16,9 +16,55 @@ use PhpParser\Node\IntersectionType; use PhpParser\Node\NullableType; use PhpParser\Node\UnionType; use PhpParser\NodeAbstract; +use PhpParser\NodeFinder; +use PhpParser\Node\Expr\Variable; trait ClosureGenerator { + protected function parseArrowFunction(Expr\ArrowFunction $expr): string + { + $nodeFinder = new NodeFinder(); + $vars = $nodeFinder->findInstanceOf($expr->expr, Variable::class); + $uses = []; + $params = []; + + foreach ($expr->params as $i => $param) { + if ($param->byRef) { + $this->fatalError($expr, 'Closure cannot use reference parameter'); + } + if ($param->var instanceof Variable) { + $params[$param->var->name] = $i; + } + } + + foreach ($vars as $var) { + $varName = $this->escapeVarName($this->parseVariable($var)); + if ($varName === 'this_' + or !$this->hasLocalVar($varName) + or isset($params[$var->name]) + or isset($uses[$varName])) { + continue; + } + $uses[$varName] = new Node\ClosureUse($var); + } + $uses = array_values($uses); + + return $this->genClosure($expr, $expr->params, $uses); + } + + protected function parseClosure(Expr\Closure $expr): string + { + return $this->genClosure($expr, $expr->params, $expr->uses); + } + + protected function isReturnStmtInLastLine(array $stmts): bool + { + if (count($stmts) === 0) { + return false; + } + return $stmts[array_key_last($stmts)] instanceof Node\Stmt\Return_; + } + protected function genScopeSwitchCode(): string { $tmpScope = $this->genTmpVarName(); diff --git a/src/Parser/ClassConstantFetchTrait.php b/src/Parser/ClassConstantFetchTrait.php new file mode 100644 index 00000000..d33efb2d --- /dev/null +++ b/src/Parser/ClassConstantFetchTrait.php @@ -0,0 +1,99 @@ +isNameExpr($expr->class)) { + return $this->parseDynamicClassConstFetch($expr); + } + + $class = $this->parseIdentifier($expr->class); + $self = false; + if ($class === 'self' or $class === 'this_') { + // Trait 读取常量,必须动态获取类名 + if ($this->classDef->trait) { + $class = 'static'; + } else { + $self = true; + $class = $this->class; + } + } + + $const = $this->escapeString($this->parseIdentifier($expr->name)); + if ($class === 'static') { + if (!$this->methodDef) { + $this->fatalError($expr, "The 'static' keyword can only be used as the class name in class methods"); + } + if ($const === 'class') { + return Symbol::getCalledClass(); + } else { + return Symbol::constant() . '(' . Symbol::getCalledCe() . ', ' . $this->getLiteralString($const) . ')'; + } + } + + if ($self or $this->isNameExpr($expr->class)) { + $class = $this->getNamespacedClassName($class); + } + if ($const === 'class') { + if ($self or $this->isNameExpr($expr->class)) { + return $this->getLiteralString($class); + } + } + if (($self or $this->isNameExpr($expr->class)) and $this->isIdExpr($expr->name)) { + if ($this->hasClass($class)) { + $classDef = $this->getClass($class); + if ($classDef->enum) { + $ce = $this->getClassEntryPtr($class); + return 'php::getEnumCase(' . $ce . ', ' . $this->getLiteralString($const) . ')'; + } + $nativeConst = $this->findNativeClassConst($expr, $class, $const); + if ($nativeConst) { + return $nativeConst; + } + } + $ce = $this->getClassEntryPtr($class); + return Symbol::constant() . '(' . $ce . ', ' . $this->getLiteralString($const) . ')'; + } + $name = $class . '::' . $const; + $name = $this->getLiteralString($name); + return Symbol::constant() . '(' . $name . ')'; + } + + protected function parseDynamicClassConstFetch(Expr\ClassConstFetch $expr): string + { + $const = $this->escapeString($this->parseIdentifier($expr->name)); + $target = $this->materializeDynamicClassConstTarget($expr->class); + + if ($const === 'class') { + return 'php::fn::get_class(' . $target . ')'; + } + + $className = '(' . $target . '.isObject() ? php::fn::get_class(' . $target . ') : ' . $target . ')'; + return Symbol::constant() . '(php::concat({' . $className . ', "::", ' . $this->getLiteralString($const) . '}))'; + } + + protected function materializeDynamicClassConstTarget(NodeAbstract $expr): string + { + $this->assertExprCanBeUsedAsValue($expr, 'class constant target'); + [$value, $beforeStmts, $afterStmts] = $this->parseExprWithCapturedStmts($expr); + $tmpVar = $this->addTmpVar(self::TYPE_VAR); + $this->appendCapturedStmtLinesToContext($beforeStmts); + $this->context->beforeStmtLines[] = $tmpVar . ' = ' . $value . ';'; + $this->appendCapturedStmtLinesToContext($afterStmts); + return $tmpVar; + } + +} + diff --git a/src/Parser/NullsafeAccessTrait.php b/src/Parser/NullsafeAccessTrait.php new file mode 100644 index 00000000..e98c4590 --- /dev/null +++ b/src/Parser/NullsafeAccessTrait.php @@ -0,0 +1,162 @@ +parseNullsafeExpr($expr); + } + + protected function parseNullsafePropertyFetchUpdate(Expr\NullsafePropertyFetch $expr): string + { + return $this->parseNodeWithUpdateAttribute( + $expr, + self::ATTR_PROPERTY_FETCH_UPDATE, + true, + fn() => $this->parseNullsafePropertyFetch($expr) + ); + } + + protected function parseNullsafeMethodCall(Expr\NullsafeMethodCall $expr): string + { + return $this->parseNullsafeExpr($expr); + } + + protected function parseNullsafeExpr( + Expr\PropertyFetch|Expr\MethodCall|Expr\NullsafePropertyFetch|Expr\NullsafeMethodCall $expr + ): string + { + $list = []; + $comment = $this->formatCppLineComment('Nullsafe Operator: ', $this->printer->prettyPrint([$expr])); + + while (1) { + if ($expr instanceof Expr\NullsafePropertyFetch) { + $list[] = ['property', $this->identifierToStr($expr->name, literal: true), $expr, true]; + $expr = $expr->var; + } elseif ($expr instanceof Expr\NullsafeMethodCall) { + $list[] = ['method', $this->identifierToStr($expr->name, literal: true), $expr->args, true]; + $expr = $expr->var; + } elseif ($expr instanceof Expr\PropertyFetch) { + $list[] = ['property', $this->identifierToStr($expr->name, literal: true), $expr, false]; + $expr = $expr->var; + } elseif ($expr instanceof Expr\MethodCall) { + $list[] = ['method', $this->identifierToStr($expr->name, literal: true), $expr->args, false]; + $expr = $expr->var; + } else { + if ($this->isVarExpr($expr)) { + $object = $this->parseIdentifier($expr); + if (!$this->hasVar($object)) { + $this->errorUndefinedVariable($expr); + } + $type = $this->getVarType($object); + if ($type === self::TYPE_OBJECT) { + break; + } + } + $object = $this->addTmpVar(self::TYPE_OBJECT); + $this->context->beforeStmtLines[] = $this->getIndent() . $object . ' = ' . $this->parseIdentifier($expr) . ';'; + break; + } + } + + $list = array_reverse($list); + $this->checkNullsafePropertyAccesses($expr, $list); + $last = array_key_last($list); + $tmpFn = $this->genTmpVarName(); + + $code = $comment . PHP_EOL . 'auto ' . $tmpFn . ' = [&]() -> ' . self::TYPE_VAR . '{' . PHP_EOL; + + foreach ($list as $key => $item) { + $tmpVar = $this->addTmpVar($key !== $last ? self::TYPE_OBJECT : self::TYPE_VAR); + if ($item[3]) { + $code .= "if ({$object}.isNull()) { return " . self::VALUE_NULL . '; }'; + } + if ($item[0] == 'property') { + $update = $this->escapeBool($this->isPropertyFetchUpdate($item[2])); + $code .= $this->getIndent() . "{$tmpVar} = {$object}.attr({$item[1]}, {$update});"; + } else { + $beforeStmtCount = count($this->context->beforeStmtLines); + $afterStmtCount = count($this->context->afterStmtLines); + $args = $this->parseCallArgs($item[2]); + $argBeforeStmts = array_slice($this->context->beforeStmtLines, $beforeStmtCount); + $argAfterStmts = array_slice($this->context->afterStmtLines, $afterStmtCount); + $this->context->beforeStmtLines = array_slice($this->context->beforeStmtLines, 0, $beforeStmtCount); + $this->context->afterStmtLines = array_slice($this->context->afterStmtLines, 0, $afterStmtCount); + if ($argBeforeStmts) { + $code .= $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $argBeforeStmts) . PHP_EOL; + } + $code .= $this->getIndent() . "{$tmpVar} = {$object}.call({$item[1]}, {$args});"; + if ($argAfterStmts) { + $code .= $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $argAfterStmts) . PHP_EOL; + } + } + $object = $tmpVar; + } + $code .= $this->getIndent() . "return {$object}; };"; + $this->context->beforeStmtLines[] = $code; + return "{$tmpFn}()"; + } + + private function containsNullsafeChain(NodeAbstract $expr): bool + { + while ($expr instanceof Expr\PropertyFetch + || $expr instanceof Expr\MethodCall + || $expr instanceof Expr\NullsafePropertyFetch + || $expr instanceof Expr\NullsafeMethodCall) { + if ($expr instanceof Expr\NullsafePropertyFetch || $expr instanceof Expr\NullsafeMethodCall) { + return true; + } + $expr = $expr->var; + } + + return false; + } + + private function checkNullsafePropertyAccesses(NodeAbstract $baseExpr, array $list): void + { + $properties = []; + foreach ($list as $item) { + if ($item[0] !== 'property') { + break; + } + + /** @var Expr\NullsafePropertyFetch $node */ + $node = $item[2]; + if (!$this->isIdExpr($node->name)) { + break; + } + + $properties[] = [ + 'node' => $node, + 'property' => $this->parseIdentifier($node->name), + ]; + } + + if (!$properties) { + return; + } + + $scope = $this->class ? $this->getFullClassName() : ''; + $results = $this->createPropertyAccessResolver()->resolveNullsafePropertyChain( + $this->detectClassOfExpr($baseExpr), + $properties, + $scope, + self::TYPE_OBJECT, + ); + foreach ($results as $index => $result) { + $this->applyNativePropertyAccessResult($properties[$index]['node'], $result); + } + } + +} +