Skip to content

Commit dcac654

Browse files
committed
Allow inferring narrowed return type
Even if an explicit return type is given, we might still infer a more narrow one based on return statements. We shouldn't pessimize this just because a type has been declared.
1 parent 2f73cbb commit dcac654

File tree

2 files changed

+56
-1
lines changed

2 files changed

+56
-1
lines changed

Zend/Optimizer/zend_inference.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3519,6 +3519,12 @@ static zend_always_inline int _zend_update_type_info(
35193519
} else {
35203520
zend_arg_info *ret_info = op_array->arg_info - 1;
35213521
tmp = zend_fetch_arg_info_type(script, ret_info, &ce);
3522+
3523+
// TODO: We could model more precisely how illegal types are converted.
3524+
uint32_t extra_types = t1 & ~tmp;
3525+
if (!extra_types) {
3526+
tmp &= t1;
3527+
}
35223528
}
35233529
if (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) {
35243530
UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
@@ -4019,6 +4025,11 @@ void zend_func_return_info(const zend_op_array *op_array,
40194025
return;
40204026
}
40214027

4028+
if (!ret->type) {
4029+
/* We will intersect the type later. */
4030+
ret->type = MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_KEY_ANY;
4031+
}
4032+
40224033
for (j = 0; j < blocks_count; j++) {
40234034
if ((blocks[j].flags & ZEND_BB_REACHABLE) && blocks[j].len != 0) {
40244035
zend_op *opline = op_array->opcodes + blocks[j].start + blocks[j].len - 1;
@@ -4178,10 +4189,10 @@ void zend_func_return_info(const zend_op_array *op_array,
41784189
if (tmp_has_range < 0) {
41794190
tmp_has_range = 0;
41804191
}
4181-
ret->type = tmp;
41824192
ret->ce = tmp_ce;
41834193
ret->is_instanceof = tmp_is_instanceof;
41844194
}
4195+
ret->type &= tmp;
41854196
ret->range = tmp_range;
41864197
ret->has_range = tmp_has_range;
41874198
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
Return type check elision
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.optimization_level=-1
7+
opcache.opt_debug_level=0x20000
8+
opcache.preload=
9+
--SKIPIF--
10+
<?php require_once('skipif.inc'); ?>
11+
--FILE--
12+
<?php
13+
14+
class Test1 {
15+
final public function getIntOrFloat(int $i): int|float {
16+
return $i;
17+
}
18+
final public function getInt(): int {
19+
return $this->getIntOrFloat();
20+
}
21+
}
22+
23+
?>
24+
--EXPECTF--
25+
$_main:
26+
; (lines=1, args=0, vars=0, tmps=0)
27+
; (after optimizer)
28+
; %s
29+
0000 RETURN int(1)
30+
31+
Test1::getIntOrFloat:
32+
; (lines=2, args=1, vars=1, tmps=0)
33+
; (after optimizer)
34+
; %s
35+
0000 CV0($i) = RECV 1
36+
0001 RETURN CV0($i)
37+
38+
Test1::getInt:
39+
; (lines=3, args=0, vars=0, tmps=1)
40+
; (after optimizer)
41+
; %s
42+
0000 INIT_METHOD_CALL 0 THIS string("getIntOrFloat")
43+
0001 V0 = DO_UCALL
44+
0002 RETURN V0

0 commit comments

Comments
 (0)