Skip to content

Commit 2edc3cf

Browse files
keluniknikic
authored andcommitted
Implement Parameter Type Widening RFC
1 parent 0ecffc8 commit 2edc3cf

File tree

5 files changed

+67
-29
lines changed

5 files changed

+67
-29
lines changed

UPGRADING

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ PHP 7.2 UPGRADE NOTES
8686
2. New Features
8787
========================================
8888

89+
- Core:
90+
. It is now possible to remove argument type annotations when overriding an
91+
inherited method. This complies with contravariance of method argument types
92+
under the Liskov Substitution Principle.
93+
(https://wiki.php.net/rfc/parameter-no-type-variance)
94+
8995
- PCRE:
9096
. Added `J` modifier for setting PCRE_DUPNAMES.
9197

Zend/tests/objects_009.phpt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,5 @@ class test3 extends test {
1919

2020
echo "Done\n";
2121
?>
22-
--EXPECTF--
23-
Warning: Declaration of test3::foo($arg) should be compatible with test::foo(Test $arg) in %s on line %d
22+
--EXPECT--
2423
Done
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--TEST--
2+
Parameter variance with no type
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
function testParentClass(Foo $foo) {}
8+
function testBothClass(Foo $foo) {}
9+
function testChildClass($foo) {}
10+
function testNoneClass($foo) {}
11+
12+
function testParentBuiltin(int $foo) {}
13+
function testBothBuiltin(int $foo) {}
14+
function testChildBuiltin($foo) {}
15+
function testNoneBuiltin($foo) {}
16+
}
17+
18+
class Bar extends Foo {
19+
function testParentClass($foo) {}
20+
function testBothClass(Foo $foo) {}
21+
function testChildClass(Foo $foo) {}
22+
function testNoneClass($foo) {}
23+
24+
function testParentBuiltin($foo) {}
25+
function testBothBuiltin(int $foo) {}
26+
function testChildBuiltin(int $foo) {}
27+
function testNoneBuiltin($foo) {}
28+
}
29+
30+
?>
31+
--EXPECTF--
32+
Warning: Declaration of Bar::testChildClass(Foo $foo) should be compatible with Foo::testChildClass($foo) in %s on line %d
33+
34+
Warning: Declaration of Bar::testChildBuiltin(int $foo) should be compatible with Foo::testChildBuiltin($foo) in %s on line %d

Zend/zend_inheritance.c

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -172,23 +172,20 @@ static zend_always_inline zend_bool zend_iterable_compatibility_check(zend_arg_i
172172
if (ZEND_TYPE_CODE(arg_info->type) == IS_ARRAY) {
173173
return 1;
174174
}
175-
175+
176176
if (ZEND_TYPE_IS_CLASS(arg_info->type) && zend_string_equals_literal_ci(ZEND_TYPE_NAME(arg_info->type), "Traversable")) {
177177
return 1;
178178
}
179-
179+
180180
return 0;
181181
}
182182
/* }}} */
183183

184184
static int zend_do_perform_type_hint_check(const zend_function *fe, zend_arg_info *fe_arg_info, const zend_function *proto, zend_arg_info *proto_arg_info) /* {{{ */
185185
{
186-
if (ZEND_LOG_XOR(ZEND_TYPE_IS_CLASS(fe_arg_info->type), ZEND_TYPE_IS_CLASS(proto_arg_info->type))) {
187-
/* Only one has a type declaration and the other one doesn't */
188-
return 0;
189-
}
186+
ZEND_ASSERT(ZEND_TYPE_IS_SET(fe_arg_info->type) && ZEND_TYPE_IS_SET(proto_arg_info->type));
190187

191-
if (ZEND_TYPE_IS_CLASS(fe_arg_info->type)) {
188+
if (ZEND_TYPE_IS_CLASS(fe_arg_info->type) && ZEND_TYPE_IS_CLASS(proto_arg_info->type)) {
192189
zend_string *fe_class_name, *proto_class_name;
193190
const char *class_name;
194191

@@ -237,14 +234,30 @@ static int zend_do_perform_type_hint_check(const zend_function *fe, zend_arg_inf
237234
zend_string_release(proto_class_name);
238235
zend_string_release(fe_class_name);
239236
} else if (ZEND_TYPE_CODE(fe_arg_info->type) != ZEND_TYPE_CODE(proto_arg_info->type)) {
240-
/* Incompatible type */
237+
/* Incompatible built-in types */
241238
return 0;
242239
}
243240

244241
return 1;
245242
}
246243
/* }}} */
247244

245+
static int zend_do_perform_arg_type_hint_check(const zend_function *fe, zend_arg_info *fe_arg_info, const zend_function *proto, zend_arg_info *proto_arg_info) /* {{{ */
246+
{
247+
if (!ZEND_TYPE_IS_SET(fe_arg_info->type)) {
248+
/* Child with no type is always compatible */
249+
return 1;
250+
}
251+
252+
if (!ZEND_TYPE_IS_SET(proto_arg_info->type)) {
253+
/* Child defines a type, but parent doesn't, violates LSP */
254+
return 0;
255+
}
256+
257+
return zend_do_perform_type_hint_check(fe, fe_arg_info, proto, proto_arg_info);
258+
}
259+
/* }}} */
260+
248261
static zend_bool zend_do_perform_implementation_check(const zend_function *fe, const zend_function *proto) /* {{{ */
249262
{
250263
uint32_t i, num_args;
@@ -312,15 +325,15 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c
312325
} else {
313326
proto_arg_info = &proto->common.arg_info[proto->common.num_args];
314327
}
315-
316-
if (!zend_do_perform_type_hint_check(fe, fe_arg_info, proto, proto_arg_info)) {
328+
329+
if (!zend_do_perform_arg_type_hint_check(fe, fe_arg_info, proto, proto_arg_info)) {
317330
switch (ZEND_TYPE_CODE(fe_arg_info->type)) {
318331
case IS_ITERABLE:
319332
if (!zend_iterable_compatibility_check(proto_arg_info)) {
320333
return 0;
321334
}
322335
break;
323-
336+
324337
default:
325338
return 0;
326339
}
@@ -345,15 +358,15 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c
345358
if (!(fe->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) {
346359
return 0;
347360
}
348-
361+
349362
if (!zend_do_perform_type_hint_check(fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1)) {
350363
switch (ZEND_TYPE_CODE(proto->common.arg_info[-1].type)) {
351364
case IS_ITERABLE:
352365
if (!zend_iterable_compatibility_check(fe->common.arg_info - 1)) {
353366
return 0;
354367
}
355368
break;
356-
369+
357370
default:
358371
return 0;
359372
}

tests/classes/type_hinting_005b.phpt

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)