Merge pull request 'fix(translator): 修复trait parent::调用的函数声明参数不一致' (#38) from translator-fix-trait-parent-funcdecl-params into master

Reviewed-on: #38
pull/40/head
韩天峰 1 month ago
commit 3fef88de8e
  1. 42
      phpunit/code/trait-aliased-constructor-parent-call.php
  2. 45
      phpunit/src/TraitFuncDeclTest.php
  3. 2
      src/Entity/FunctionDef.php
  4. 39
      src/Translator.php
  5. 61
      tests/compiler/trait/trait-aliased-constructor-parent-call.phpt

@ -0,0 +1,42 @@
<?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);
}
}
class DirectDriver extends Base
{
use TPdoDriver;
}

@ -0,0 +1,45 @@
<?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->assertMatchesRegularExpression(
'/extern void php_tpdodriver____construct\([^;\n]*trait_parent_ce[^;\n]*\);/',
$decl,
'The trait function declaration must include its implicit parent scope'
);
$this->assertDoesNotMatchRegularExpression(
'/extern void php_(?:driver__tpdodriverconstruct|directdriver____construct)'
. '\([^;\n]*trait_parent_ce[^;\n]*\);/',
$decl,
'Composing-class wrapper declarations must not expose the implicit parent scope'
);
}
}

@ -33,6 +33,8 @@ class FunctionDef
public string $attributeFactoryScope = '';
/** External library imported by the stub containing this function. */
public string $importLibrary = '';
/** Whether the native signature includes an implicit trait parent scope. */
public bool $hasTraitParentCeParameter = false;
public bool $returnTypeUndeclared = false;
public bool $returnsByRef = false;
public bool $generator = false;

@ -1602,6 +1602,9 @@ CODE;
$list = [];
if ($func->method) {
$list[] = Type::OBJECT . ' &this_';
if ($func->hasTraitParentCeParameter) {
$list[] = 'zend_class_entry *trait_parent_ce';
}
}
$argInfoList = $func->argInfoList;
if ($argInfoList) {
@ -3459,12 +3462,14 @@ CODE;
$cppReturnType = $multiReturn
? $this->functionDef->getMultiReturnCppType()
: ($this->functionDef->returnsByRef ? Type::REF : $this->getReturnType());
$this->functionDef->hasTraitParentCeParameter =
$this->classDef?->trait !== null && (bool) $this->methodDef?->parentMethodCalls;
$nativeName = self::PREFIX . $name;
$functionAttribute = $this->getFunctionOptimizationAttribute($this->functionDef);
$functionDeclCode = $functionAttribute . $cppReturnType . ' ' . ($multiReturn ? $this->getMultiReturnImplName($name) : $nativeName) . '(';
if ($this->class) {
$functionDeclCode .= Type::OBJECT . ' &this_';
if ($this->classDef?->trait !== null && $this->methodDef?->parentMethodCalls) {
if ($this->functionDef->hasTraitParentCeParameter) {
$functionDeclCode .= ', zend_class_entry *trait_parent_ce';
}
if ($this->functionDef->params) {
@ -4462,6 +4467,10 @@ CODE;
string $traitMethodName,
string $classMethodName
): string {
// A trait may be composed by multiple classes and aliases. Each wrapper
// needs independent signature metadata.
$methodDef = clone $methodDef;
// A trait method's `self`/`static`/`parent` return and parameter types
// refer to the class that uses the trait, not the trait itself. Re-resolve
// them to the consuming class so signature-compatibility checks (against
@ -4470,6 +4479,10 @@ CODE;
// function untouched.
$this->reresolveTraitLateBoundTypes($classDef, $methodDef);
// The wrapper computes the composing class's parent scope and passes it
// to the trait function; it does not expose that scope in its signature.
$methodDef->functionDef->hasTraitParentCeParameter = 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
@ -4540,28 +4553,8 @@ 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 has a separate native signature from the trait function.
$newFn = clone $fn;
if ($fn->returnTypeKeyword !== '') {
$resolved = $this->resolveLateBoundClass($usingClassDef, $fn->returnTypeKeyword);

@ -0,0 +1,61 @@
--TEST--
Trait parent constructor calls keep declarations aligned for aliased and direct wrappers
--FILE--
<?php
class Base
{
public function __construct(array $options = [])
{
var_dump($options);
}
}
trait DriverConstructor
{
public function __construct(array $options = [])
{
$options['trait'] = 1;
parent::__construct($options);
}
}
class AliasedDriver extends Base
{
use DriverConstructor {
__construct as private traitConstruct;
}
public function __construct(array $options = [])
{
$options['alias'] = 1;
$this->traitConstruct($options);
}
}
class DirectDriver extends Base
{
use DriverConstructor;
}
function main()
{
new AliasedDriver(['input' => 1]);
new DirectDriver(['direct' => 1]);
}
?>
--EXPECT--
array(3) {
["input"]=>
int(1)
["alias"]=>
int(1)
["trait"]=>
int(1)
}
array(2) {
["direct"]=>
int(1)
["trait"]=>
int(1)
}
Loading…
Cancel
Save