]> granicus.if.org Git - php/commitdiff
Allow inferring narrowed return type
authorNikita Popov <nikita.ppv@gmail.com>
Thu, 18 Mar 2021 16:11:56 +0000 (17:11 +0100)
committerNikita Popov <nikita.ppv@gmail.com>
Thu, 18 Mar 2021 16:11:56 +0000 (17:11 +0100)
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.

Zend/Optimizer/zend_inference.c
ext/opcache/tests/opt/verify_return_type.phpt [new file with mode: 0644]

index 42f37fa0c9f155d36e526a1900df45072b5a5b94..e5d476d01d5bc747fcbbdda0f0da8ee9ed84bee8 100644 (file)
@@ -3519,6 +3519,12 @@ static zend_always_inline int _zend_update_type_info(
                        } else {
                                zend_arg_info *ret_info = op_array->arg_info - 1;
                                tmp = zend_fetch_arg_info_type(script, ret_info, &ce);
+
+                               // TODO: We could model more precisely how illegal types are converted.
+                               uint32_t extra_types = t1 & ~tmp;
+                               if (!extra_types) {
+                                       tmp &= t1;
+                               }
                        }
                        if (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) {
                                UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
@@ -4019,6 +4025,11 @@ void zend_func_return_info(const zend_op_array   *op_array,
                return;
        }
 
+       if (!ret->type) {
+               /* We will intersect the type later. */
+               ret->type = MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_KEY_ANY;
+       }
+
        for (j = 0; j < blocks_count; j++) {
                if ((blocks[j].flags & ZEND_BB_REACHABLE) && blocks[j].len != 0) {
                        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,
                if (tmp_has_range < 0) {
                        tmp_has_range = 0;
                }
-               ret->type = tmp;
                ret->ce = tmp_ce;
                ret->is_instanceof = tmp_is_instanceof;
        }
+       ret->type &= tmp;
        ret->range = tmp_range;
        ret->has_range = tmp_has_range;
 }
diff --git a/ext/opcache/tests/opt/verify_return_type.phpt b/ext/opcache/tests/opt/verify_return_type.phpt
new file mode 100644 (file)
index 0000000..c1384c7
--- /dev/null
@@ -0,0 +1,44 @@
+--TEST--
+Return type check elision
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+opcache.opt_debug_level=0x20000
+opcache.preload=
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+
+class Test1 {
+    final public function getIntOrFloat(int $i): int|float {
+        return $i;
+    }
+    final public function getInt(): int {
+        return $this->getIntOrFloat();
+    }
+}
+
+?>
+--EXPECTF--
+$_main:
+     ; (lines=1, args=0, vars=0, tmps=0)
+     ; (after optimizer)
+     ; %s
+0000 RETURN int(1)
+
+Test1::getIntOrFloat:
+     ; (lines=2, args=1, vars=1, tmps=0)
+     ; (after optimizer)
+     ; %s
+0000 CV0($i) = RECV 1
+0001 RETURN CV0($i)
+
+Test1::getInt:
+     ; (lines=3, args=0, vars=0, tmps=1)
+     ; (after optimizer)
+     ; %s
+0000 INIT_METHOD_CALL 0 THIS string("getIntOrFloat")
+0001 V0 = DO_UCALL
+0002 RETURN V0