Three defects in composeTraitAst diverged from PHP trait semantics:
1. When a later trait supplied a concrete method for a name an earlier
trait declared abstract, the branch unset the new concrete statement
while the already-merged abstract one stayed in the class AST. The
composed class kept only the abstract declaration, so instantiating
it failed with "Cannot instantiate abstract class" even though the
method body existed. The reverse trait order worked. Now the merged
abstract declaration is dropped and the concrete method is kept.
2. Alias adaptations assigned the new modifier over the whole flag set,
wiping static/final/abstract: `use Maker { make as protected; }`
turned a static method into an instance method, and static:: calls
on it miscompiled. PHP replaces only the visibility bits (and keeps
the original visibility when the modifier carries none, e.g.
`as final`).
3. A method defined by the class itself did not suppress the
trait-vs-trait conflict check, so `class C { use A, B; function f(){} }`
with f() in both traits died with a spurious "method already exists"
fatal. The class-method check now runs before conflict resolution and
suppressed trait copies are no longer registered as trait methods.
master
parent
b493ac79c5
commit
a0be8bf349
2 changed files with 87 additions and 7 deletions
@ -0,0 +1,51 @@ |
|||||||
|
--TEST-- |
||||||
|
Trait composition: concrete fulfills abstract, alias keeps static, class method wins |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
// A concrete trait method fulfills an abstract requirement from another |
||||||
|
// trait, regardless of the order the traits are listed in. |
||||||
|
trait NeedsName { abstract public function name(): string; } |
||||||
|
trait HasName { public function name(): string { return "HasName"; } } |
||||||
|
class AbstractFirst { use NeedsName, HasName; } |
||||||
|
class ConcreteFirst { use HasName, NeedsName; } |
||||||
|
|
||||||
|
// An alias visibility change keeps the `static` flag. |
||||||
|
trait Maker { |
||||||
|
public static function make(): string { return "made"; } |
||||||
|
} |
||||||
|
class Factory { |
||||||
|
use Maker { make as protected; } |
||||||
|
public static function build(): string { return static::make(); } |
||||||
|
} |
||||||
|
|
||||||
|
// An alias under a new name keeps the `static` flag too. |
||||||
|
trait Counter { |
||||||
|
public static function count7(): int { return 7; } |
||||||
|
} |
||||||
|
class Stats { |
||||||
|
use Counter { count7 as protected seven; } |
||||||
|
public static function total(): int { return static::seven(); } |
||||||
|
} |
||||||
|
|
||||||
|
// The class's own method wins over two same-name trait methods without |
||||||
|
// this counting as a trait-vs-trait conflict. |
||||||
|
trait WhoA { public function who(): string { return "WhoA"; } } |
||||||
|
trait WhoB { public function who(): string { return "WhoB"; } } |
||||||
|
class Self1 { use WhoA, WhoB; public function who(): string { return "Self1"; } } |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
echo (new AbstractFirst())->name(), "\n"; |
||||||
|
echo (new ConcreteFirst())->name(), "\n"; |
||||||
|
echo Factory::build(), "\n"; |
||||||
|
echo Stats::total(), "\n"; |
||||||
|
echo (new Self1())->who(), "\n"; |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
HasName |
||||||
|
HasName |
||||||
|
made |
||||||
|
7 |
||||||
|
Self1 |
||||||
Loading…
Reference in new issue