fix(compiler): resolve __METHOD__ in ordinary functions (#125) --skip-tests

* fix(compiler): resolve __METHOD__ in ordinary functions

* fix(compiler): preserve lexical magic names in closures
master
yavon007 3 weeks ago committed by GitHub
parent 63a4793e0c
commit 1425499588
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      src/CompilerBase.php
  2. 4
      src/Context/FunctionContext.php
  3. 25
      src/Generator/ClosureGenerator.php
  4. 9
      src/Parser/ConstantExpressionTrait.php
  5. 98
      tests/compiler/namespace/magic-closure-names.phpt
  6. 14
      tests/compiler/namespace/magic-function-method.phpt

@ -4684,6 +4684,7 @@ class CompilerBase implements PropertyAccessContext
$this->context = new FunctionContext();
$this->context->arguments = $oriCtx->localVars;
$this->context->closureMagicName = $oriCtx->closureMagicName;
// Outer locals are captured arguments. New initializer temporaries
// must not reuse their names and inherit an incompatible scalar type.
$this->context->tmpVarIndex = $oriCtx->tmpVarIndex;

@ -134,6 +134,9 @@ class FunctionContext
/** True when the innermost enclosing breakable construct is a switch, not a loop. */
public bool $breakableIsSwitch = false;
public bool $inClosure = false;
/** Lexical PHP closure name; null for named functions, including generated callbacks. */
public ?string $closureMagicName = null;
public ?array $closureReturnTypeCheck = null;
public string $closureReturnTypeStr = '';
@ -196,6 +199,7 @@ class FunctionContext
$this->inLoop = false;
$this->inContinuableLoop = false;
$this->inClosure = false;
$this->closureMagicName = null;
$this->closureReturnTypeCheck = null;
$this->closureReturnTypeStr = '';
}

@ -126,6 +126,7 @@ trait ClosureGenerator
$this->resolveTypeDecl($expr->returnType, self::DECL_TYPE_OF_RETURN);
}
$closureMagicName = $this->getClosureMagicName($expr);
$entryContext = $this->context;
$entryIndent = $this->indentLevel;
$entryInGeneratorBody = $this->inGeneratorBody;
@ -140,6 +141,7 @@ trait ClosureGenerator
try {
$this->context = new FunctionContext();
$this->context->inClosure = true;
$this->context->closureMagicName = $closureMagicName;
$this->inGeneratorBody = false;
$this->indentLevel = $entryIndent + 1;
@ -352,6 +354,26 @@ trait ClosureGenerator
return 'php::UserCodeScopeGuard ' . $tmpScope . '{' . $this->getCallableScopeExpr() . '};' . PHP_EOL;
}
private function getClosureMagicName(Expr\ArrowFunction|Expr\Closure $expr): string
{
$parent = $this->context->closureMagicName;
if ($parent === null) {
if ($this->methodDef !== null) {
$class = $this->methodDef->traitOrigin !== ''
? $this->methodDef->traitOrigin
: $this->classDef->getNamespacedName(false);
$method = $this->methodDef->node?->getAttribute('typephp_trait_method', $this->method)
?? $this->method;
$parent = $class . '::' . $method . '()';
} elseif ($this->function !== '') {
$parent = ($this->namespace !== '' ? $this->namespace . '\\' : '') . $this->function . '()';
} else {
$parent = $this->file;
}
}
return '{closure:' . $parent . ':' . $expr->getStartLine() . '}';
}
protected function genClosure(Expr\ArrowFunction|Expr\Closure $expr, array $params, array $uses = []): string
{
$entryContext = $this->context;
@ -458,10 +480,12 @@ trait ClosureGenerator
. Type::ARGS . ' &vars_) ' .
'-> ' . Type::VAR . ' {' . PHP_EOL;
$closureMagicName = $this->getClosureMagicName($expr);
$oriContext = $this->context;
$this->context = new FunctionContext();
$this->context->inClosure = true;
$this->context->closureMagicName = $closureMagicName;
$body = $expr instanceof Expr\ArrowFunction ? $expr->expr : $expr->stmts;
$this->prepareReferenceCaptureDegradations($body);
if (!$isGenerator
@ -676,6 +700,7 @@ trait ClosureGenerator
$this->context = new FunctionContext();
$this->context->inClosure = true;
$this->context->closureMagicName = $outerContext->closureMagicName;
$this->inGeneratorBody = true;
$this->indentLevel++;

@ -158,6 +158,9 @@ trait ConstantExpressionTrait
// with PHP, where it is an empty string outside a property.
return '""';
case 'Scalar_MagicConst_Function':
if ($this->context->closureMagicName !== null) {
return '"' . $this->escapeString($this->context->closureMagicName) . '"';
}
return '"' . $this->escapeString($function) . '"';
case 'Scalar_MagicConst_Class':
if (!$this->classDef) {
@ -176,6 +179,12 @@ trait ConstantExpressionTrait
}
return '"' . $this->escapeString($class) . '"';
case 'Scalar_MagicConst_Method':
if ($this->context->closureMagicName !== null) {
return '"' . $this->escapeString($this->context->closureMagicName) . '"';
}
if ($this->methodDef === null) {
return '"' . $this->escapeString($function) . '"';
}
return '"' . $this->escapeString($class) . '::' . $this->escapeString($this->method) . '"';
default:
$this->unsupportedSyntax($expr);

@ -0,0 +1,98 @@
--TEST--
Closure and arrow function magic names preserve their lexical context
--FILE--
<?php
namespace MagicClosureNames {
function outer(): void {
$closure = function (): void {
echo __FUNCTION__, "\n", __METHOD__, "\n";
static $name = __METHOD__;
echo $name, "\n";
$nested = fn(): string => __METHOD__;
echo $nested(), "\n";
};
$closure();
$arrow = fn(): string => __METHOD__;
echo $arrow(), "\n";
echo __METHOD__, "\n";
}
function dynamicClosure(): \Closure {
return function (): string { return __METHOD__; };
}
function dynamicArrow(): \Closure {
return fn(): string => __FUNCTION__;
}
function generatorClosure(): void {
$generator = function () {
yield __METHOD__;
$nested = fn(): string => __METHOD__;
yield $nested();
};
foreach ($generator() as $name) {
echo $name, "\n";
}
echo __METHOD__, "\n";
}
trait Names {
public function original(): void {
$closure = fn(): string => __METHOD__;
echo $closure(), "\n";
}
}
class UsesNames {
use Names { original as renamed; }
}
class Example {
public static function method(): void {
$closure = function (): string { return __METHOD__; };
echo $closure(), "\n";
$arrow = fn(): string => __FUNCTION__;
echo $arrow(), "\n";
echo __METHOD__, "\n";
}
}
}
namespace {
function globalOuter(): void {
$closure = function (): string { return __METHOD__; };
echo $closure(), "\n";
$arrow = fn(): string => __METHOD__;
echo $arrow(), "\n";
}
function main(): void {
\MagicClosureNames\outer();
\MagicClosureNames\Example::method();
globalOuter();
(new \MagicClosureNames\UsesNames())->renamed();
$closure = \MagicClosureNames\dynamicClosure();
echo $closure(), "\n";
$arrow = \MagicClosureNames\dynamicArrow();
echo $arrow(), "\n";
\MagicClosureNames\generatorClosure();
}
}
?>
--EXPECT--
{closure:MagicClosureNames\outer():4}
{closure:MagicClosureNames\outer():4}
{closure:MagicClosureNames\outer():4}
{closure:{closure:MagicClosureNames\outer():4}:8}
{closure:MagicClosureNames\outer():12}
MagicClosureNames\outer
{closure:MagicClosureNames\Example::method():50}
{closure:MagicClosureNames\Example::method():52}
MagicClosureNames\Example::method
{closure:globalOuter():60}
{closure:globalOuter():62}
{closure:MagicClosureNames\Names::original():39}
{closure:MagicClosureNames\dynamicClosure():18}
{closure:MagicClosureNames\dynamicArrow():22}
{closure:MagicClosureNames\generatorClosure():26}
{closure:{closure:MagicClosureNames\generatorClosure():26}:28}
MagicClosureNames\generatorClosure

@ -1,5 +1,5 @@
--TEST--
__FUNCTION__ uses the short name in namespaced methods
__FUNCTION__ and __METHOD__ preserve function and method names
--FILE--
<?php
namespace MagicFunctionMethod {
@ -14,15 +14,21 @@ namespace MagicFunctionMethod {
}
function namespacedFunction(): void {
echo __FUNCTION__, "\n";
echo __FUNCTION__, "\n", __METHOD__, "\n";
}
}
namespace {
function globalFunction(): void {
echo __FUNCTION__, "\n", __METHOD__, "\n";
}
function main(): void {
(new \MagicFunctionMethod\Example())->instanceMethod();
\MagicFunctionMethod\Example::staticMethod();
\MagicFunctionMethod\namespacedFunction();
globalFunction();
echo __METHOD__, "\n";
}
}
?>
@ -32,3 +38,7 @@ MagicFunctionMethod\Example::instanceMethod
staticMethod
MagicFunctionMethod\Example::staticMethod
MagicFunctionMethod\namespacedFunction
MagicFunctionMethod\namespacedFunction
globalFunction
globalFunction
main

Loading…
Cancel
Save