Skip to content

Commit 4866602

Browse files
morrisonlevinikic
authored andcommitted
Add covariance/contravariance to inherited methods
Return types are covariant; parameter types are contravariant.
1 parent 0122f39 commit 4866602

22 files changed

+1514
-1059
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
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Undefined types during variance checks are warnings
3+
--FILE--
4+
<?php
5+
6+
class X {
7+
function m(stdClass $z) {}
8+
}
9+
10+
class Y extends X {
11+
function m(UndefinedA $z) {}
12+
}
13+
?>
14+
--EXPECTF--
15+
Warning: Declaration of Y::m(UndefinedA $z) should be compatible with X::m(stdClass $z) in %s on line %d
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Undefined types during variance checks are warnings
3+
--FILE--
4+
<?php
5+
6+
abstract class X {
7+
function m(stdClass $z) {}
8+
}
9+
10+
class Y extends X {
11+
function m(UndefinedA $z) {}
12+
}
13+
?>
14+
--EXPECTF--
15+
Warning: Declaration of Y::m(UndefinedA $z) should be compatible with X::m(stdClass $z) in %s on line %d
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Undefined types during variance checks are errors
3+
--FILE--
4+
<?php
5+
6+
abstract class X {
7+
abstract function m(stdClass $z);
8+
}
9+
10+
class Y extends X {
11+
function m(UndefinedA $z) {}
12+
}
13+
?>
14+
--EXPECTF--
15+
Fatal error: Declaration of Y::m(UndefinedA $z) must be compatible with X::m(stdClass $z) in %s on line %d
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Undefined types during variance checks are errors
3+
--FILE--
4+
<?php
5+
6+
interface X {
7+
function m(stdClass $z);
8+
}
9+
10+
class Y implements X {
11+
function m(UndefinedA $z) {}
12+
}
13+
?>
14+
--EXPECTF--
15+
Fatal error: Declaration of Y::m(UndefinedA $z) must be compatible with X::m(stdClass $z) in %s on line %d
16+

Zend/zend.c

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

632+
compiler_globals->unverified_types = NULL;
633+
632634
zend_set_default_compile_time_values();
633635

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

Zend/zend_compile.c

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3965,6 +3965,26 @@ void zend_compile_static_call(znode *result, zend_ast *ast, uint32_t type) /* {{
39653965

39663966
zend_op *zend_compile_class_decl(zend_ast *ast, zend_bool toplevel);
39673967

3968+
static
3969+
void _backup_unverified_variance_types(HashTable *unverified_types,
3970+
HashTable **prev_unverified_types)
3971+
{
3972+
zend_hash_init(unverified_types, 0, NULL, NULL, 1);
3973+
*prev_unverified_types = CG(unverified_types);
3974+
CG(unverified_types) = unverified_types;
3975+
}
3976+
3977+
static void _compile_verify_variance(HashTable *unverified_types)
3978+
{
3979+
zend_string *lcname;
3980+
ZEND_HASH_FOREACH_STR_KEY(unverified_types, lcname) {
3981+
zend_op *opline = get_next_op();
3982+
opline->op1_type = IS_CONST;
3983+
opline->opcode = ZEND_VERIFY_VARIANCE;
3984+
LITERAL_STR(opline->op1, zend_string_copy(lcname));
3985+
} ZEND_HASH_FOREACH_END();
3986+
}
3987+
39683988
void zend_compile_new(znode *result, zend_ast *ast) /* {{{ */
39693989
{
39703990
zend_ast *class_ast = ast->child[0];
@@ -3974,11 +3994,22 @@ void zend_compile_new(znode *result, zend_ast *ast) /* {{{ */
39743994
zend_op *opline;
39753995

39763996
if (class_ast->kind == ZEND_AST_CLASS) {
3997+
/* backup previous unverified variance list; anon classes are immediately verified */
3998+
HashTable unverified_types;
3999+
HashTable *prev_unverified_types;
4000+
_backup_unverified_variance_types(&unverified_types, &prev_unverified_types);
4001+
39774002
/* jump over anon class declaration */
39784003
opline = zend_compile_class_decl(class_ast, 0);
39794004
class_node.op_type = opline->result_type;
39804005
class_node.u.op.var = opline->result.var;
39814006
opline->extended_value = get_next_op_number();
4007+
4008+
_compile_verify_variance(&unverified_types);
4009+
4010+
zend_hash_destroy(&unverified_types);
4011+
CG(unverified_types) = prev_unverified_types;
4012+
39824013
} else {
39834014
zend_compile_class_ref(&class_node, class_ast, ZEND_FETCH_CLASS_EXCEPTION);
39844015
}
@@ -6308,6 +6339,14 @@ zend_op *zend_compile_class_decl(zend_ast *ast, zend_bool toplevel) /* {{{ */
63086339
ce->ce_flags |= ZEND_ACC_TOP_LEVEL;
63096340
}
63106341

6342+
if (extends_ast || implements_ast) {
6343+
if (CG(unverified_types)) {
6344+
zend_hash_add_empty_element(CG(unverified_types), lcname);
6345+
} else {
6346+
// todo: figure out why it's null; need a caller (somewhere) to initialize, emit, and destroy the unverified types
6347+
}
6348+
}
6349+
63116350
if (toplevel
63126351
/* We currently don't early-bind classes that implement interfaces or use traits */
63136352
&& !(ce->ce_flags & (ZEND_ACC_IMPLEMENT_INTERFACES|ZEND_ACC_IMPLEMENT_TRAITS))) {
@@ -8139,6 +8178,35 @@ void zend_const_expr_to_zval(zval *result, zend_ast *ast) /* {{{ */
81398178
}
81408179
/* }}} */
81418180

8181+
static zend_bool _is_type_decl(zend_ast *ast) {
8182+
return ast && ast->kind == ZEND_AST_CLASS;
8183+
}
8184+
8185+
static zend_bool _is_not_decl_stmt(zend_ast *ast) {
8186+
if (ast) {
8187+
/* todo: what else should be considered a decl stmt? */
8188+
switch (ast->kind) {
8189+
case ZEND_AST_FUNC_DECL:
8190+
case ZEND_AST_CLASS:
8191+
return 0;
8192+
8193+
default:
8194+
return 1;
8195+
}
8196+
}
8197+
8198+
/* todo: why are these sometimes null? */
8199+
return 0;
8200+
}
8201+
8202+
static zend_ast **_ast_find(zend_ast **begin, zend_ast **end,
8203+
zend_bool (*pred)(zend_ast *)) {
8204+
for (; begin < end; ++begin)
8205+
if (pred(*begin))
8206+
return begin;
8207+
return begin;
8208+
}
8209+
81428210
/* Same as compile_stmt, but with early binding */
81438211
void zend_compile_top_stmt(zend_ast *ast) /* {{{ */
81448212
{
@@ -8148,9 +8216,37 @@ void zend_compile_top_stmt(zend_ast *ast) /* {{{ */
81488216

81498217
if (ast->kind == ZEND_AST_STMT_LIST) {
81508218
zend_ast_list *list = zend_ast_get_list(ast);
8151-
uint32_t i;
8152-
for (i = 0; i < list->children; ++i) {
8153-
zend_compile_top_stmt(list->child[i]);
8219+
zend_ast **begin = list->child;
8220+
zend_ast **end = begin + list->children;
8221+
zend_ast **first_decl = _ast_find(begin, end, &_is_type_decl);
8222+
zend_ast **last_decl = _ast_find(first_decl, end, &_is_not_decl_stmt);
8223+
zend_ast **p;
8224+
8225+
/* Compile opcodes before first type decl */
8226+
for (p = begin; p < first_decl; ++p) {
8227+
zend_compile_top_stmt(*p);
8228+
}
8229+
8230+
/* Compile decl stmts */
8231+
{
8232+
HashTable unverified_types;
8233+
HashTable *prev_unverified_types;
8234+
_backup_unverified_variance_types(&unverified_types, &prev_unverified_types);
8235+
8236+
for (p = first_decl; p < last_decl; ++p) {
8237+
zend_compile_top_stmt(*p);
8238+
}
8239+
8240+
_compile_verify_variance(&unverified_types);
8241+
8242+
zend_hash_destroy(&unverified_types);
8243+
CG(unverified_types) = prev_unverified_types;
8244+
}
8245+
8246+
/* Compile remainder */
8247+
/* todo: loop to catch any non-consecutive type declarations */
8248+
for (p = last_decl; p < end; ++p) {
8249+
zend_compile_top_stmt(*p);
81548250
}
81558251
return;
81568252
}

Zend/zend_globals.h

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

6969
zend_class_entry *active_class_entry;
70+
HashTable *unverified_types;
7071

7172
zend_string *compiled_filename;
7273

0 commit comments

Comments
 (0)