]> granicus.if.org Git - php/commitdiff
Add void return type
authorAndrea Faulds <ajf@ajf.me>
Wed, 14 Oct 2015 18:15:32 +0000 (19:15 +0100)
committerAndrea Faulds <ajf@ajf.me>
Wed, 18 Nov 2015 17:30:49 +0000 (17:30 +0000)
NEWS
UPGRADING
Zend/tests/return_types/void_allowed.phpt [new file with mode: 0644]
Zend/tests/return_types/void_disallowed1.phpt [new file with mode: 0644]
Zend/tests/return_types/void_disallowed2.phpt [new file with mode: 0644]
Zend/tests/return_types/void_parameter.phpt [new file with mode: 0644]
Zend/zend_API.c
Zend/zend_compile.c
Zend/zend_execute.c
Zend/zend_types.h

diff --git a/NEWS b/NEWS
index 1d9fd2a100798428f3b3b567880beec4f27c9e80..c33379064b81ba905e6cc8fa6202a8421c9cc7c7 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,7 @@ PHP                                                                        NEWS
 
 Core:
   . Fixed bug #62210 (Exceptions can leak temporary variables). (Dmitry, Bob)
+  . Added void return type. (Andrea)
 
 Standard
   . Implemented FR #55716 (Add an option to pass a custom stream context to
index 44516e90799afa7aad4be36eb0e8738d2e7d2865..8929404d985a0910a310e7bbff33769b3f2e398b 100644 (file)
--- a/UPGRADING
+++ b/UPGRADING
@@ -18,10 +18,16 @@ PHP 7.1 UPGRADE NOTES
 ========================================
 1. Backward Incompatible Changes
 ========================================
+- Core:
+  . 'void' can no longer be used as the name of a class, interface, or trait.
+    This applies to declarations, class_alias() and use statements.
 
 ========================================
 2. New Features
 ========================================
+- Core
+  . Added void return type, which requires that a function not return a value.
+    (RFC: https://wiki.php.net/rfc/void_return_type)
 
 ========================================
 3. Changes in SAPI modules
diff --git a/Zend/tests/return_types/void_allowed.phpt b/Zend/tests/return_types/void_allowed.phpt
new file mode 100644 (file)
index 0000000..8f07c73
--- /dev/null
@@ -0,0 +1,20 @@
+--TEST--
+void return type: acceptable cases
+--FILE--
+<?php
+
+function foo(): void {
+    // okay
+}
+
+foo();
+
+function bar(): void {
+    return; // okay
+}
+
+bar();
+
+echo "OK!", PHP_EOL;
+--EXPECT--
+OK!
diff --git a/Zend/tests/return_types/void_disallowed1.phpt b/Zend/tests/return_types/void_disallowed1.phpt
new file mode 100644 (file)
index 0000000..b20f129
--- /dev/null
@@ -0,0 +1,12 @@
+--TEST--
+void return type: unacceptable cases: explicit NULL return
+--FILE--
+<?php
+
+function foo(): void {
+    return NULL; // not permitted in a void function
+}
+
+// Note the lack of function call: function validated at compile-time
+--EXPECTF--
+Fatal error: A void function must not return a value in %s on line %d
diff --git a/Zend/tests/return_types/void_disallowed2.phpt b/Zend/tests/return_types/void_disallowed2.phpt
new file mode 100644 (file)
index 0000000..7bbc3ac
--- /dev/null
@@ -0,0 +1,12 @@
+--TEST--
+void return type: unacceptable cases: explicit return of some other value
+--FILE--
+<?php
+
+function foo(): void {
+    return -1; // not permitted in a void function
+}
+
+// Note the lack of function call: function validated at compile-time
+--EXPECTF--
+Fatal error: A void function must not return a value in %s on line %d
diff --git a/Zend/tests/return_types/void_parameter.phpt b/Zend/tests/return_types/void_parameter.phpt
new file mode 100644 (file)
index 0000000..4c6e918
--- /dev/null
@@ -0,0 +1,8 @@
+--TEST--
+void return type: not valid as a parameter type
+--FILE--
+<?php
+
+function foobar(void $a) {}
+--EXPECTF--
+Fatal error: void cannot be used as a parameter type in %s on line %d
index 5a488f7e9d662fa1ad79f19629e4fee2f69ccee6..384599b90e70b623d98ca6509bb609a0d8c4c179 100644 (file)
@@ -183,6 +183,8 @@ ZEND_API char *zend_get_type_by_const(int type) /* {{{ */
                        return "callable";
                case IS_ARRAY:
                        return "array";
+               case IS_VOID:
+                       return "void";
                default:
                        return "unknown";
        }
index a987dcef837e405030b4d991b7218581842a22b7..092c7af4664fabca1df1643432370a352a661a86 100644 (file)
@@ -153,6 +153,7 @@ static const struct reserved_class_name reserved_class_names[] = {
        {ZEND_STRL("static")},
        {ZEND_STRL("string")},
        {ZEND_STRL("true")},
+       {ZEND_STRL("void")},
        {NULL, 0}
 };
 
@@ -196,6 +197,7 @@ static const builtin_type_info builtin_types[] = {
        {ZEND_STRL("float"), IS_DOUBLE},
        {ZEND_STRL("string"), IS_STRING},
        {ZEND_STRL("bool"), _IS_BOOL},
+       {ZEND_STRL("void"), IS_VOID},
        {NULL, 0, IS_UNDEF}
 };
 
@@ -2164,6 +2166,11 @@ static zend_op *zend_delayed_compile_end(uint32_t offset) /* {{{ */
 
 static void zend_emit_return_type_check(znode *expr, zend_arg_info *return_info) /* {{{ */
 {
+       /* `return ...;` is illegal in a void function (but `return;` isn't) */
+       if (expr && return_info->type_hint == IS_VOID) {
+               zend_error_noreturn(E_COMPILE_ERROR, "A void function must not return a value");
+       }
+
        if (return_info->type_hint != IS_UNDEF) {
                zend_op *opline = zend_emit_op(NULL, ZEND_VERIFY_RETURN_TYPE, expr, NULL);
                if (expr && expr->op_type == IS_CONST) {
@@ -4706,6 +4713,10 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */
 
                        zend_compile_typename(type_ast, arg_info);
 
+                       if (arg_info->type_hint == IS_VOID) {
+                               zend_error_noreturn(E_COMPILE_ERROR, "void cannot be used as a parameter type");
+                       }
+
                        if (type_ast->kind == ZEND_AST_TYPE) {
                                if (arg_info->type_hint == IS_ARRAY) {
                                        if (default_ast && !has_null_default
index aa48b06767e230e614fc92d7ffbf590662281910..044b49e7606eb8d7a53330267786172906b0e2e0 100644 (file)
@@ -946,6 +946,24 @@ static ZEND_COLD void zend_verify_internal_return_error(const zend_function *zf,
                fclass, fsep, fname, need_msg, need_kind, returned_msg, returned_kind);
 }
 
+static ZEND_COLD void zend_verify_void_return_error(const zend_function *zf, const char *returned_msg, const char *returned_kind)
+{
+       const char *fname = ZSTR_VAL(zf->common.function_name);
+       const char *fsep;
+       const char *fclass;
+
+       if (zf->common.scope) {
+               fsep =  "::";
+               fclass = ZSTR_VAL(zf->common.scope->name);
+       } else {
+               fsep =  "";
+               fclass = "";
+       }
+
+       zend_type_error("%s%s%s() must not return a value, %s%s returned",
+               fclass, fsep, fname, returned_msg, returned_kind);
+}
+
 #if ZEND_DEBUG
 static int zend_verify_internal_return_type(zend_function *zf, zval *ret)
 {
@@ -975,6 +993,8 @@ static int zend_verify_internal_return_type(zend_function *zf, zval *ret)
                        } else if (ret_info->type_hint == _IS_BOOL &&
                                   EXPECTED(Z_TYPE_P(ret) == IS_FALSE || Z_TYPE_P(ret) == IS_TRUE)) {
                                /* pass */
+                       } else if (ret_info->type_hint == IS_VOID) {
+                               zend_verify_void_return_error(zf, zend_zval_type_name(ret), "");
                        } else {
                                /* Use strict check to verify return value of internal function */
                                zend_verify_internal_return_error(zf, "be of the type ", zend_get_type_by_const(ret_info->type_hint), zend_zval_type_name(ret), "");
@@ -1035,6 +1055,12 @@ static zend_always_inline void zend_verify_return_type(zend_function *zf, zval *
                        } else if (ret_info->type_hint == _IS_BOOL &&
                                   EXPECTED(Z_TYPE_P(ret) == IS_FALSE || Z_TYPE_P(ret) == IS_TRUE)) {
                                /* pass */
+                       /* There would be a check here for the IS_VOID type hint, which
+                        * would trigger an error because a value had been returned.
+                        * However, zend_compile.c already does a compile-time check
+                        * that bans `return ...;` within a void function. Thus we can skip
+                        * this part of the runtime check for non-internal functions.
+                        */
                        } else if (UNEXPECTED(!zend_verify_scalar_type_hint(ret_info->type_hint, ret, ZEND_RET_USES_STRICT_TYPES()))) {
                                zend_verify_return_error(zf, "be of the type ", zend_get_type_by_const(ret_info->type_hint), zend_zval_type_name(ret), "");
                        }
@@ -1048,7 +1074,7 @@ static ZEND_COLD int zend_verify_missing_return_type(zend_function *zf, void **c
        char *need_msg;
        zend_class_entry *ce;
 
-       if (ret_info->type_hint) {
+       if (ret_info->type_hint && EXPECTED(ret_info->type_hint != IS_VOID)) {
                if (ret_info->class_name) {
                        if (EXPECTED(*cache_slot)) {
                                ce = (zend_class_entry*)*cache_slot;
index 9d15f498f8449094058e95bd5ca0592d614271cc..805ea6d8c729999dda9e1f7d6b99e7fb8e80c098 100644 (file)
@@ -318,6 +318,7 @@ struct _zend_ast_ref {
 /* fake types */
 #define _IS_BOOL                                       13
 #define IS_CALLABLE                                    14
+#define IS_VOID                                                18
 
 /* internal types */
 #define IS_INDIRECT                    15