-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Fix prototype for trait methods #14016
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
--TEST-- | ||
GH-14009: Traits inherit prototype | ||
--FILE-- | ||
<?php | ||
|
||
class P { | ||
protected function common() { | ||
throw new Exception('Unreachable'); | ||
} | ||
} | ||
|
||
class A extends P { | ||
public function test(P $sibling) { | ||
$sibling->common(); | ||
} | ||
} | ||
|
||
class B extends P { | ||
protected function common() { | ||
echo __METHOD__, "\n"; | ||
} | ||
} | ||
|
||
trait T { | ||
protected function common() { | ||
echo __METHOD__, "\n"; | ||
} | ||
} | ||
|
||
class C extends P { | ||
use T; | ||
} | ||
|
||
$a = new A(); | ||
$a->test(new B()); | ||
$a->test(new C()); | ||
|
||
?> | ||
--EXPECT-- | ||
B::common | ||
T::common |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
--TEST-- | ||
GH-14009: Traits inherit prototype | ||
--FILE-- | ||
<?php | ||
|
||
class P { | ||
protected function common() {} | ||
} | ||
|
||
class A extends P {} | ||
|
||
trait T { | ||
private abstract function common(int $param); | ||
} | ||
|
||
class B extends P { | ||
use T; | ||
} | ||
|
||
?> | ||
--EXPECTF-- | ||
Fatal error: Declaration of P::common() must be compatible with T::common(int $param) in %s on line %d |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
--TEST-- | ||
GH-14009: Traits inherit prototype | ||
--FILE-- | ||
<?php | ||
|
||
class A { | ||
private function test() {} | ||
} | ||
|
||
trait T { | ||
private function test() {} | ||
} | ||
|
||
class B extends A { | ||
use T; | ||
} | ||
|
||
?> | ||
--EXPECT-- |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
--TEST-- | ||
GH-14009: Traits inherit prototype | ||
--FILE-- | ||
<?php | ||
|
||
class A { | ||
protected function test() { | ||
throw new Exception('Unreachable'); | ||
} | ||
} | ||
|
||
class B extends A { | ||
// Prototype is A::test(). | ||
protected function test() { | ||
echo __METHOD__, "\n"; | ||
} | ||
} | ||
|
||
trait T { | ||
protected abstract function test(); | ||
} | ||
|
||
class C extends B { | ||
use T; | ||
} | ||
|
||
class D extends A { | ||
public static function callTest($sibling) { | ||
$sibling->test(); | ||
} | ||
} | ||
|
||
D::callTest(new C()); | ||
|
||
?> | ||
--EXPECT-- | ||
B::test |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
--TEST-- | ||
Interfaces don't set prototypes to their parent method | ||
--FILE-- | ||
<?php | ||
|
||
interface A { | ||
public function __construct(int $param); | ||
} | ||
interface B extends A { | ||
public function __construct(int|float $param); | ||
} | ||
class Test implements B { | ||
public function __construct(int $param) {} | ||
} | ||
new Test(42); | ||
|
||
?> | ||
--EXPECTF-- | ||
Fatal error: Declaration of Test::__construct(int $param) must be compatible with B::__construct(int|float $param) in %s on line %d |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
--TEST-- | ||
Interfaces don't set prototypes to their parent method | ||
--XFAIL-- | ||
X::__constructor()'s prototype is set to B::__construct(). Y::__construct() then | ||
uses prototype to verify LSP, but misses A::__construct() which has a stricter | ||
signature. | ||
--FILE-- | ||
<?php | ||
|
||
interface A { | ||
public function __construct(int|float $param); | ||
} | ||
interface B { | ||
public function __construct(int $param); | ||
} | ||
class X implements A, B { | ||
public function __construct(int|float $param) {} | ||
} | ||
class Y extends X { | ||
public function __construct(int $param) {} | ||
} | ||
new Y(42); | ||
|
||
?> | ||
--EXPECTF-- | ||
Fatal error: Declaration of Y::__construct(int $param) must be compatible with A::__construct(int|float $param) in %s on line %d |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1085,8 +1085,19 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex( | |
uint32_t parent_flags = parent->common.fn_flags; | ||
zend_function *proto; | ||
|
||
#define SEPARATE_METHOD() do { \ | ||
if (child_scope != ce && child->type == ZEND_USER_FUNCTION && child_zv) { \ | ||
/* op_array wasn't duplicated yet */ \ | ||
zend_function *new_function = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); \ | ||
memcpy(new_function, child, sizeof(zend_op_array)); \ | ||
Z_PTR_P(child_zv) = child = new_function; \ | ||
child_zv = NULL; \ | ||
} \ | ||
} while(0) | ||
|
||
if (UNEXPECTED((parent_flags & ZEND_ACC_PRIVATE) && !(parent_flags & ZEND_ACC_ABSTRACT) && !(parent_flags & ZEND_ACC_CTOR))) { | ||
if (!check_only) { | ||
SEPARATE_METHOD(); | ||
child->common.fn_flags |= ZEND_ACC_CHANGED; | ||
} | ||
/* The parent method is private and not an abstract so we don't need to check any inheritance rules */ | ||
|
@@ -1130,7 +1141,12 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex( | |
ZEND_FN_SCOPE_NAME(parent), ZSTR_VAL(child->common.function_name), ZEND_FN_SCOPE_NAME(child)); | ||
} | ||
|
||
if (!check_only && (parent_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_CHANGED))) { | ||
if (!check_only | ||
&& (parent_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_CHANGED)) | ||
/* In the case of abstract traits, don't mark the method as changed. We don't actually have a | ||
* private parent method. */ | ||
&& !(parent->common.scope->ce_flags & ZEND_ACC_TRAIT)) { | ||
SEPARATE_METHOD(); | ||
child->common.fn_flags |= ZEND_ACC_CHANGED; | ||
} | ||
|
||
|
@@ -1146,21 +1162,16 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex( | |
parent = proto; | ||
} | ||
|
||
if (!check_only && child->common.prototype != proto && child_zv) { | ||
do { | ||
if (child->common.scope != ce && child->type == ZEND_USER_FUNCTION) { | ||
if (ce->ce_flags & ZEND_ACC_INTERFACE) { | ||
/* Few parent interfaces contain the same method */ | ||
break; | ||
} else { | ||
/* op_array wasn't duplicated yet */ | ||
zend_function *new_function = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); | ||
memcpy(new_function, child, sizeof(zend_op_array)); | ||
Z_PTR_P(child_zv) = child = new_function; | ||
} | ||
} | ||
child->common.prototype = proto; | ||
} while (0); | ||
if (!check_only | ||
&& child->common.prototype != proto | ||
/* We are not setting the prototype of overridden interface methods because of abstract | ||
* constructors. See Zend/tests/interface_constructor_prototype_001.phpt. */ | ||
&& !(ce->ce_flags & ZEND_ACC_INTERFACE) | ||
/* Parent is a trait when its method is abstract. We only need to verify its signature then, | ||
* don't actually use it as a prototype. See Zend/tests/gh14009_004.phpt. */ | ||
&& !(parent->common.scope->ce_flags & ZEND_ACC_TRAIT)) { | ||
Comment on lines
+1170
to
+1172
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this comment is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, that was my first thought. But I then realized that this completely disables the error messages, and adding yet another parameter to the already confusing ones is something I was trying to avoid. |
||
SEPARATE_METHOD(); | ||
child->common.prototype = proto; | ||
} | ||
|
||
/* Prevent derived classes from restricting access that was available in parent classes (except deriving from non-abstract ctors) */ | ||
|
@@ -1180,6 +1191,9 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex( | |
} | ||
perform_delayable_implementation_check(ce, child, child_scope, parent, parent_scope); | ||
} | ||
|
||
#undef SEPARATE_METHOD | ||
|
||
return INHERITANCE_SUCCESS; | ||
} | ||
/* }}} */ | ||
|
Uh oh!
There was an error while loading. Please reload this page.