fix(translator): 修复trait parent::调用的函数声明参数不一致

pull/38/head
Yurun 1 month ago
parent 2287695b44
commit 7be7240d5d
  1. 37
      phpunit/code/trait-aliased-constructor-parent-call.php
  2. 39
      phpunit/src/TraitFuncDeclTest.php
  3. 8
      src/Entity/FunctionDef.php
  4. 44
      src/Translator.php

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
class Base
{
public function __construct(array $option = [])
{
echo 'Base:';
foreach ($option as $k => $v) {
echo ' ' . $k . '=' . $v;
}
echo "\n";
}
}
trait TPdoDriver
{
public function __construct(array $option = [])
{
$option['fromTrait'] = 1;
parent::__construct($option);
}
}
class Driver extends Base
{
use TPdoDriver {
__construct as private tPdoDriverConstruct;
}
public function __construct(array $option = [])
{
$option['username'] = 'postgres';
$this->tPdoDriverConstruct($option);
}
}

@ -0,0 +1,39 @@
<?php
/**
* Regression test for the trait `parent::` / `trait_parent_ce` declaration bug.
*
* A trait method whose body contains `parent::` calls is compiled with an implicit
* `zend_class_entry *trait_parent_ce` parameter (so `parent::` can be bound to the
* class that composes the trait). The shared `func_decl.h` declaration must emit the
* same parameter; otherwise the generated C++ fails to compile with C2660
* ("function does not accept 3 arguments") at the call site that forwards to the trait
* function. This is a code-generation-level check that fails before the fix and passes
* after it.
*/
class TraitFuncDeclTest extends \BaseTest
{
public function testAliasedTraitConstructorParentCallDeclaresTraitParentCe(): void
{
// BaseTest::compile() populates the global $translator and translates the file.
$this->compile('trait-aliased-constructor-parent-call.php');
global $translator;
$compiler = $translator;
$headerPath = $compiler->getIncludeDir() . '/php_trait_parent_ce_func_decl.h';
if (file_exists($headerPath)) {
@unlink($headerPath);
}
// Emit the shared function-declaration header (genFunctionDeclarations), which
// is what the fix targets.
$compiler->genFunctionDeclarations($headerPath);
$decl = file_get_contents($headerPath);
$this->assertStringContainsString(
'trait_parent_ce',
$decl,
'func_decl.h must declare the implicit trait_parent_ce parameter for trait methods with parent:: calls'
);
}
}

@ -33,6 +33,14 @@ class FunctionDef
public string $attributeFactoryScope = '';
/** External library imported by the stub containing this function. */
public string $importLibrary = '';
/**
* True for a trait method whose body contains `parent::` calls. Such methods
* receive an implicit `zend_class_entry *trait_parent_ce` parameter (right after
* `this_`) so the `parent::` call can be bound to the class that composes the
* trait. Both the definition and the shared `func_decl.h` declaration must emit
* this parameter, otherwise the declaration/definition signatures disagree.
*/
public bool $traitParentCe = false;
public bool $returnTypeUndeclared = false;
public bool $returnsByRef = false;
public bool $generator = false;

@ -1602,6 +1602,12 @@ CODE;
$list = [];
if ($func->method) {
$list[] = Type::OBJECT . ' &this_';
// A trait method with `parent::` calls receives an implicit
// `trait_parent_ce` parameter right after `this_`. The definition
// adds it (see parseFunction); the declaration must match.
if ($func->traitParentCe) {
$list[] = 'zend_class_entry *trait_parent_ce';
}
}
$argInfoList = $func->argInfoList;
if ($argInfoList) {
@ -3464,6 +3470,9 @@ CODE;
$functionDeclCode .= Type::OBJECT . ' &this_';
if ($this->classDef?->trait !== null && $this->methodDef?->parentMethodCalls) {
$functionDeclCode .= ', zend_class_entry *trait_parent_ce';
// Record the implicit parameter so the shared `func_decl.h`
// declaration (genFunctionDeclaration) emits the same signature.
$this->functionDef->traitParentCe = true;
}
if ($this->functionDef->params) {
$functionDeclCode .= ', ';
@ -4320,6 +4329,14 @@ CODE;
// function untouched.
$this->reresolveTraitLateBoundTypes($classDef, $methodDef);
// The wrapper is a method of the *composing* class, not a trait method, so
// it must not receive the implicit `trait_parent_ce` parameter (it computes
// the parent class entry itself when forwarding to the trait function). The
// cloned FunctionDef inherited `traitParentCe` from the trait's FunctionDef;
// clear it so the shared `func_decl.h` declaration matches the wrapper's
// own (2-parameter) definition.
$methodDef->functionDef->traitParentCe = false;
// Validate `parent::` calls emitted from this trait method against the
// parent of the class that is composing the trait. The trait itself has
// no parent at compile time, so this is the only place the parent class
@ -4390,28 +4407,11 @@ CODE;
private function reresolveTraitLateBoundTypes(ClassDef $usingClassDef, MethodDef $methodDef): void
{
$fn = $methodDef->functionDef;
$needsClone = false;
if ($fn->returnTypeKeyword !== '') {
$resolved = $this->resolveLateBoundClass($usingClassDef, $fn->returnTypeKeyword);
if ($resolved !== null && $resolved !== $fn->returnClass) {
$needsClone = true;
}
}
foreach ($fn->argInfoList as $arg) {
if ($arg->typeKeyword !== '') {
$resolved = $this->resolveLateBoundClass($usingClassDef, $arg->typeKeyword);
if ($resolved !== null && ($resolved !== $arg->class || $resolved !== $arg->declaredClass)) {
$needsClone = true;
break;
}
}
}
if (!$needsClone) {
return;
}
// Always produce a distinct FunctionDef for the composing-class wrapper.
// The wrapper is a separate method (different name, and no implicit
// `trait_parent_ce` parameter) from the trait's own function, so it must
// not share the trait's FunctionDef object — mutating one (e.g. clearing
// `traitParentCe`) would otherwise leak into the trait's declaration.
$newFn = clone $fn;
if ($fn->returnTypeKeyword !== '') {
$resolved = $this->resolveLateBoundClass($usingClassDef, $fn->returnTypeKeyword);

Loading…
Cancel
Save