Skip to content
2 changes: 1 addition & 1 deletion Zend/tests/bug62441.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ namespace ns {
}
?>
--EXPECTF--
Fatal error: Declaration of ns\Foo::method(ns\stdClass $o) must be compatible with Iface::method(stdClass $o) in %s on line %d
Fatal error: Could not check compatibility between ns\Foo::method(ns\stdClass $o) and Iface::method(stdClass $o), because class ns\stdClass is not available in %s on line %d
4 changes: 4 additions & 0 deletions Zend/tests/bug76451.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php

class Foo {}
class_alias('Foo', 'Bar');
16 changes: 16 additions & 0 deletions Zend/tests/bug76451.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
Bug #76451: Aliases during inheritance type checks affected by opcache
--FILE--
<?php
require __DIR__ . "/bug76451.inc";

class A {
public function test(Foo $foo) {}
}
class B extends A {
public function test(Bar $foo) {}
}
?>
===DONE===
--EXPECT--
===DONE===
8 changes: 8 additions & 0 deletions Zend/tests/bug76451_2.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php
class A {
public function test(Foo $foo) {}
}
class B extends A {
public function test(Bar $foo) {}
}
?>
12 changes: 12 additions & 0 deletions Zend/tests/bug76451_2.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Bug #76451: Aliases during inheritance type checks affected by opcache (variation)
--FILE--
<?php
class Foo {}
class_alias('Foo', 'Bar');

require __DIR__ . '/bug76451_2.inc';
?>
===DONE===
--EXPECT--
===DONE===
8 changes: 5 additions & 3 deletions Zend/tests/return_types/008.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class qux implements foo {
}

$qux = new qux();
var_dump($qux->bar());
--EXPECTF--
Fatal error: Declaration of qux::bar(): qux must be compatible with foo::bar(): foo in %s008.php on line 8
echo get_class($qux->bar());

?>
--EXPECT--
qux
8 changes: 5 additions & 3 deletions Zend/tests/return_types/generators003.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class SomeCollection implements Collection {
}

$some = new SomeCollection();
var_dump($some->getIterator());
--EXPECTF--
Fatal error: Declaration of SomeCollection::getIterator(): Generator must be compatible with Collection::getIterator(): Iterator in %sgenerators003.php on line 7
echo get_class($some->getIterator());

?>
--EXPECT--
Generator
8 changes: 6 additions & 2 deletions Zend/tests/return_types/inheritance005.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,9 @@ class Bar extends Foo {
return new Bar;
}
}
--EXPECTF--
Fatal error: Declaration of Bar::test(): Bar must be compatible with Foo::test(): Foo in %sinheritance005.php on line 9

echo get_class(Bar::test());

?>
--EXPECT--
Bar
8 changes: 6 additions & 2 deletions Zend/tests/return_types/inheritance006.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@ class Bar extends Foo {
return new B;
}
}
--EXPECTF--
Fatal error: Declaration of Bar::test(): B must be compatible with Foo::test(): A in %sinheritance006.php on line 11

echo get_class(Bar::test());

?>
--EXPECT--
B
8 changes: 6 additions & 2 deletions Zend/tests/return_types/inheritance007.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,9 @@ class Bar extends Foo {
return new ArrayObject([1, 2]);
}
}
--EXPECTF--
Fatal error: Declaration of Bar::test(): ArrayObject must be compatible with Foo::test(): Traversable in %sinheritance007.php on line 9

echo get_class(Bar::test());

?>
--EXPECT--
ArrayObject
19 changes: 19 additions & 0 deletions Zend/tests/type_declarations/variance/class_order.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
Returns are covariant, but we don't allow the code due to class ordering
--FILE--
<?php

class A {
public function method() : B {}
}
class B extends A {
public function method() : C {}
}
class C extends B {
}

new C;

?>
--EXPECTF--
Fatal error: Could not check compatibility between B::method(): C and A::method(): B, because class C is not available in %s on line %d
25 changes: 25 additions & 0 deletions Zend/tests/type_declarations/variance/class_order_autoload.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
--TEST--
Returns are covariant, but we don't allow the code due to class ordering (autoload variation)
--FILE--
<?php

spl_autoload_register(function($class) {
if ($class === 'A') {
class A {
public function method() : B {}
}
} else if ($class == 'B') {
class B extends A {
public function method() : C {}
}
} else {
class C extends B {
}
}
});

$c = new C;

?>
--EXPECTF--
Fatal error: Could not check compatibility between B::method(): C and A::method(): B, because class C is not available in %s on line %d
26 changes: 26 additions & 0 deletions Zend/tests/type_declarations/variance/enum_forward_compat.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
Forward compatibility with types that look like classes but aren't
--FILE--
<?php

spl_autoload_register(function($class) {
var_dump($class);
if ($class === 'X') {
class X {}
} else {
class Y {}
}
});

class A {
public function method(X $param) : object {}
}

class B extends A {
public function method(object $param) : Y {}
}

?>
--EXPECT--
string(1) "X"
string(1) "Y"
22 changes: 22 additions & 0 deletions Zend/tests/type_declarations/variance/object_variance.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
Testing object's variance in inheritance
--FILE--
<?php

interface I1 {
function method1(I1 $o): object;
}
interface I2 extends I1 {
function method1(object $o): I1;
}
final class C1 implements I2 {
function method1($o = null): self {
return $this;
}
}

$o = new C1();
echo get_class($o->method1());
?>
--EXPECT--
C1
45 changes: 45 additions & 0 deletions Zend/tests/type_declarations/variance/parent_in_class.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
--TEST--
Use of parent inside a class that has / has no parent
--FILE--
<?php

// Illegal: A::parent is ill-defined
class A {
public function method(parent $x) {}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this allowed at all?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had originally added a compile-time check for this in 7.4, but reverted it again in deb44d4 because it broke Mockery, which is used by Laravel. Maybe we can do this for PHP 8.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be a good idea to add this into deprecations for PHP-7.4

class B extends A {
public function method(parent $x) {}
}

// Legal: A2::parent == P2
class P2 {}
class A2 extends P2 {
public function method(parent $x) {}
}
class B2 extends A2 {
public function method(P2 $x) {}
}

// Legal: B3::parent == A3 is subclass of A3::parent == P3 in covariant position
class P3 {}
class A3 extends P3 {
public function method($x): parent {}
}
class B3 extends A3 {
public function method($x): parent {}
}

// Illegal: B4::parent == A4 is subclass of A4::parent == P4 in contravariant position
class P4 {}
class A4 extends P4 {
public function method(parent $x) {}
}
class B4 extends A4 {
public function method(parent $x) {}
}

?>
--EXPECTF--
Warning: Declaration of B4::method(A4 $x) should be compatible with A4::method(P4 $x) in %s on line %d

Warning: Could not check compatibility between B::method(A $x) and A::method(parent $x), because class parent is not available in %s on line %d
9 changes: 6 additions & 3 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1131,14 +1131,16 @@ ZEND_API void zend_do_delayed_early_binding(const zend_op_array *op_array, uint3
if (first_early_binding_opline != (uint32_t)-1) {
zend_bool orig_in_compilation = CG(in_compilation);
uint32_t opline_num = first_early_binding_opline;
zend_class_entry *ce;

CG(in_compilation) = 1;
while (opline_num != (uint32_t)-1) {
const zend_op *opline = &op_array->opcodes[opline_num];
zval *lcname = RT_CONSTANT(opline, opline->op1);
zval *parent_name = RT_CONSTANT(opline, opline->op2);
if ((ce = zend_lookup_class_ex(Z_STR_P(parent_name), Z_STR_P(parent_name + 1), 0)) != NULL) {
do_bind_class(RT_CONSTANT(&op_array->opcodes[opline_num], op_array->opcodes[opline_num].op1), ce);
zend_class_entry *ce = zend_hash_find_ptr(EG(class_table), Z_STR_P(lcname + 1));
zend_class_entry *parent_ce = zend_lookup_class_ex(Z_STR_P(parent_name), Z_STR_P(parent_name + 1), 0);
if (ce && parent_ce && zend_can_early_bind(ce, parent_ce)) {
do_bind_class(lcname, parent_ce);
}
opline_num = op_array->opcodes[opline_num].result.opline_num;
}
Expand Down Expand Up @@ -6392,6 +6394,7 @@ zend_op *zend_compile_class_decl(zend_ast *ast, zend_bool toplevel) /* {{{ */
&& !(CG(compiler_options) & ZEND_COMPILE_PRELOAD) /* delay inheritance till preloading */
&& ((parent_ce->type != ZEND_INTERNAL_CLASS) || !(CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_CLASSES))
&& ((parent_ce->type != ZEND_USER_CLASS) || !(CG(compiler_options) & ZEND_COMPILE_IGNORE_OTHER_FILES) || (parent_ce->info.user.filename == ce->info.user.filename))
&& zend_can_early_bind(ce, parent_ce)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should try to reduce overhead of zend_can_early_bind(), but we can do it later.

) {
if (EXPECTED(zend_hash_add_ptr(CG(class_table), lcname, ce) != NULL)) {
CG(zend_lineno) = decl->end_lineno;
Expand Down
Loading