Skip to content

Commit d4022ca

Browse files
committed
Add covariance/contravariance to inherited methods
Return types are covariant; parameter types are contravariant.
1 parent f1ede80 commit d4022ca

18 files changed

+1371
-978
lines changed

Zend/tests/bug76451.inc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
3+
class Foo {}
4+
class_alias('Foo', 'Bar');

Zend/tests/bug76451.phpt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
Aliases during inheritance type checks affected by opcache
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.optimization_level=-1
7+
--SKIPIF--
8+
<?php if (!extension_loaded('Zend OPcache') || php_sapi_name() != "cli") die("skip CLI only"); ?>
9+
--FILE--
10+
<?php
11+
require __DIR__ . "/bug76451.inc";
12+
13+
class A {
14+
public function test(Foo $foo) {}
15+
}
16+
class B extends A {
17+
public function test(Bar $foo) {}
18+
}
19+
?>
20+
--EXPECT--
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
Testing object's variance in inheritance
3+
--FILE--
4+
<?php
5+
6+
interface I1 {
7+
function method1(I1 $o): object;
8+
}
9+
interface I2 extends I1 {
10+
function method1(object $o): I1;
11+
}
12+
final class C1 implements I2 {
13+
function method1($o = null): self {
14+
return $this;
15+
}
16+
}
17+
18+
$o = new C1();
19+
echo get_class($o->method1());
20+
?>
21+
--EXPECT--
22+
C1

Zend/tests/return_types/008.phpt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class qux implements foo {
1414
}
1515

1616
$qux = new qux();
17-
var_dump($qux->bar());
18-
--EXPECTF--
19-
Fatal error: Declaration of qux::bar(): qux must be compatible with foo::bar(): foo in %s008.php on line 7
17+
echo get_class($qux->bar());
18+
19+
--EXPECT--
20+
qux

Zend/tests/return_types/generators003.phpt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class SomeCollection implements Collection {
1515
}
1616

1717
$some = new SomeCollection();
18-
var_dump($some->getIterator());
19-
--EXPECTF--
20-
Fatal error: Declaration of SomeCollection::getIterator(): Generator must be compatible with Collection::getIterator(): Iterator in %sgenerators003.php on line 6
18+
echo get_class($some->getIterator());
19+
20+
--EXPECT--
21+
Generator

Zend/tests/return_types/inheritance005.phpt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,8 @@ class Bar extends Foo {
1313
return new Bar;
1414
}
1515
}
16-
--EXPECTF--
17-
Fatal error: Declaration of Bar::test(): Bar must be compatible with Foo::test(): Foo in %sinheritance005.php on line 12
16+
17+
echo get_class(Bar::test());
18+
19+
--EXPECT--
20+
Bar

Zend/tests/return_types/inheritance006.phpt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,8 @@ class Bar extends Foo {
1717
return new B;
1818
}
1919
}
20-
--EXPECTF--
21-
Fatal error: Declaration of Bar::test(): B must be compatible with Foo::test(): A in %sinheritance006.php on line 14
20+
21+
echo get_class(Bar::test());
22+
23+
--EXPECT--
24+
B

Zend/tests/return_types/inheritance007.phpt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,8 @@ class Bar extends Foo {
1515
return new ArrayObject([1, 2]);
1616
}
1717
}
18-
--EXPECTF--
19-
Fatal error: Declaration of Bar::test(): ArrayObject must be compatible with Foo::test(): Traversable in %sinheritance007.php on line 12
18+
19+
echo get_class(Bar::test());
20+
21+
--EXPECT--
22+
ArrayObject

Zend/zend.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,8 @@ static void compiler_globals_ctor(zend_compiler_globals *compiler_globals) /* {{
627627
zend_hash_init_ex(compiler_globals->class_table, 64, NULL, ZEND_CLASS_DTOR, 1, 0);
628628
zend_hash_copy(compiler_globals->class_table, global_class_table, zend_class_add_ref);
629629

630+
compiler_globals->unverified_types = NULL;
631+
630632
zend_set_default_compile_time_values();
631633

632634
compiler_globals->auto_globals = (HashTable *) malloc(sizeof(HashTable));

Zend/zend_compile.c

Lines changed: 112 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4086,6 +4086,27 @@ void zend_compile_static_call(znode *result, zend_ast *ast, uint32_t type) /* {{
40864086
}
40874087
/* }}} */
40884088

4089+
4090+
static
4091+
void _backup_unverified_variance_types(HashTable *unverified_types,
4092+
HashTable **prev_unverified_types)
4093+
{
4094+
zend_hash_init(unverified_types, 0, NULL, NULL, 1);
4095+
*prev_unverified_types = CG(unverified_types);
4096+
CG(unverified_types) = unverified_types;
4097+
}
4098+
4099+
static void _compile_verify_variance(HashTable *unverified_types)
4100+
{
4101+
zend_string *lcname;
4102+
ZEND_HASH_FOREACH_STR_KEY(unverified_types, lcname) {
4103+
zend_op *opline = get_next_op();
4104+
opline->op1_type = IS_CONST;
4105+
opline->opcode = ZEND_VERIFY_VARIANCE;
4106+
LITERAL_STR(opline->op1, zend_string_copy(lcname));
4107+
} ZEND_HASH_FOREACH_END();
4108+
}
4109+
40894110
void zend_compile_class_decl(zend_ast *ast, zend_bool toplevel);
40904111

40914112
void zend_compile_new(znode *result, zend_ast *ast) /* {{{ */
@@ -4097,16 +4118,29 @@ void zend_compile_new(znode *result, zend_ast *ast) /* {{{ */
40974118
zend_op *opline;
40984119

40994120
if (class_ast->kind == ZEND_AST_CLASS) {
4100-
uint32_t dcl_opnum = get_next_op_number();
4101-
zend_compile_class_decl(class_ast, 0);
4102-
/* jump over anon class declaration */
4103-
opline = &CG(active_op_array)->opcodes[dcl_opnum];
4104-
if (opline->opcode == ZEND_FETCH_CLASS) {
4105-
opline++;
4106-
}
4107-
class_node.op_type = opline->result_type;
4108-
class_node.u.op.var = opline->result.var;
4109-
opline->extended_value = get_next_op_number();
4121+
/* backup previous unverified variance list; anon classes are immediately verified */
4122+
HashTable unverified_types;
4123+
HashTable *prev_unverified_types;
4124+
_backup_unverified_variance_types(&unverified_types, &prev_unverified_types);
4125+
4126+
{
4127+
uint32_t dcl_opnum = get_next_op_number();
4128+
zend_compile_class_decl(class_ast, 0);
4129+
/* jump over anon class declaration */
4130+
opline = &CG(active_op_array)->opcodes[dcl_opnum];
4131+
if (opline->opcode == ZEND_FETCH_CLASS) {
4132+
opline++;
4133+
}
4134+
class_node.op_type = opline->result_type;
4135+
class_node.u.op.var = opline->result.var;
4136+
opline->extended_value = get_next_op_number();
4137+
}
4138+
4139+
_compile_verify_variance(&unverified_types);
4140+
4141+
zend_hash_destroy(&unverified_types);
4142+
CG(unverified_types) = prev_unverified_types;
4143+
41104144
} else {
41114145
zend_compile_class_ref(&class_node, class_ast, ZEND_FETCH_CLASS_EXCEPTION);
41124146
}
@@ -6364,6 +6398,14 @@ void zend_compile_class_decl(zend_ast *ast, zend_bool toplevel) /* {{{ */
63646398
ce->ce_flags |= ZEND_ACC_TOP_LEVEL;
63656399
}
63666400

6401+
if (extends_ast || implements_ast) {
6402+
if (CG(unverified_types)) {
6403+
zend_hash_add_empty_element(CG(unverified_types), lcname);
6404+
} else {
6405+
// todo: figure out why it's null; need a caller (somewhere) to initialize, emit, and destroy the unverified types
6406+
}
6407+
}
6408+
63676409
if (toplevel
63686410
/* We currently don't early-bind classes that implement interfaces or use traits */
63696411
&& !(ce->ce_flags & (ZEND_ACC_IMPLEMENT_INTERFACES|ZEND_ACC_IMPLEMENT_TRAITS))) {
@@ -8041,6 +8083,35 @@ void zend_const_expr_to_zval(zval *result, zend_ast *ast) /* {{{ */
80418083
}
80428084
/* }}} */
80438085

8086+
static zend_bool _is_type_decl(zend_ast *ast) {
8087+
return ast && ast->kind == ZEND_AST_CLASS;
8088+
}
8089+
8090+
static zend_bool _is_not_decl_stmt(zend_ast *ast) {
8091+
if (ast) {
8092+
/* todo: what else should be considered a decl stmt? */
8093+
switch (ast->kind) {
8094+
case ZEND_AST_FUNC_DECL:
8095+
case ZEND_AST_CLASS:
8096+
return 0;
8097+
8098+
default:
8099+
return 1;
8100+
}
8101+
}
8102+
8103+
/* todo: why are these sometimes null? */
8104+
return 0;
8105+
}
8106+
8107+
static zend_ast **_ast_find(zend_ast **begin, zend_ast **end,
8108+
zend_bool (*pred)(zend_ast *)) {
8109+
for (; begin < end; ++begin)
8110+
if (pred(*begin))
8111+
return begin;
8112+
return begin;
8113+
}
8114+
80448115
/* Same as compile_stmt, but with early binding */
80458116
void zend_compile_top_stmt(zend_ast *ast) /* {{{ */
80468117
{
@@ -8050,9 +8121,37 @@ void zend_compile_top_stmt(zend_ast *ast) /* {{{ */
80508121

80518122
if (ast->kind == ZEND_AST_STMT_LIST) {
80528123
zend_ast_list *list = zend_ast_get_list(ast);
8053-
uint32_t i;
8054-
for (i = 0; i < list->children; ++i) {
8055-
zend_compile_top_stmt(list->child[i]);
8124+
zend_ast **begin = list->child;
8125+
zend_ast **end = begin + list->children;
8126+
zend_ast **first_decl = _ast_find(begin, end, &_is_type_decl);
8127+
zend_ast **last_decl = _ast_find(first_decl, end, &_is_not_decl_stmt);
8128+
zend_ast **p;
8129+
8130+
/* Compile opcodes before first type decl */
8131+
for (p = begin; p < first_decl; ++p) {
8132+
zend_compile_top_stmt(*p);
8133+
}
8134+
8135+
/* Compile decl stmts */
8136+
{
8137+
HashTable unverified_types;
8138+
HashTable *prev_unverified_types;
8139+
_backup_unverified_variance_types(&unverified_types, &prev_unverified_types);
8140+
8141+
for (p = first_decl; p < last_decl; ++p) {
8142+
zend_compile_top_stmt(*p);
8143+
}
8144+
8145+
_compile_verify_variance(&unverified_types);
8146+
8147+
zend_hash_destroy(&unverified_types);
8148+
CG(unverified_types) = prev_unverified_types;
8149+
}
8150+
8151+
/* Compile remainder */
8152+
/* todo: loop to catch any non-consecutive type declarations */
8153+
for (p = last_decl; p < end; ++p) {
8154+
zend_compile_top_stmt(*p);
80568155
}
80578156
return;
80588157
}

Zend/zend_globals.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ struct _zend_compiler_globals {
6565
zend_stack loop_var_stack;
6666

6767
zend_class_entry *active_class_entry;
68+
HashTable *unverified_types;
6869

6970
zend_string *compiled_filename;
7071

0 commit comments

Comments
 (0)