Skip to content

Commit d5d2d03

Browse files
committed
Support noreturn types
1 parent 495bf5c commit d5d2d03

13 files changed

+117
-5
lines changed

Zend/Optimizer/zend_dump.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,9 @@ ZEND_API void zend_dump_op(const zend_op_array *op_array, const zend_basic_block
471471
case IS_VOID:
472472
fprintf(stderr, " (void)");
473473
break;
474+
case IS_NORETURN:
475+
fprintf(stderr, " (noreturn)");
476+
break;
474477
default:
475478
fprintf(stderr, " (\?\?\?)");
476479
break;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
noreturn return type: acceptable cases
3+
--FILE--
4+
<?php
5+
6+
function foo(): noreturn {
7+
throw new Exception('bad');
8+
}
9+
10+
try {
11+
foo();
12+
} catch (Exception $e) {
13+
// do nothing
14+
}
15+
16+
function calls_foo(): noreturn {
17+
foo();
18+
}
19+
20+
try {
21+
calls_foo();
22+
} catch (Exception $e) {
23+
// do nothing
24+
}
25+
26+
echo "OK!", PHP_EOL;
27+
?>
28+
--EXPECT--
29+
OK!
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
noreturn return type: unacceptable cases: any return
3+
--FILE--
4+
<?php
5+
6+
function foo(): noreturn {
7+
return "hello"; // not permitted in a noreturn function
8+
}
9+
10+
// Note the lack of function call: function validated at compile-time
11+
?>
12+
--EXPECTF--
13+
Fatal error: A noreturn function must not return in %s on line %d
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
noreturn return type: unacceptable cases: empty return
3+
--FILE--
4+
<?php
5+
6+
function foo(): noreturn {
7+
return; // not permitted in a noreturn function
8+
}
9+
10+
// Note the lack of function call: function validated at compile-time
11+
?>
12+
--EXPECTF--
13+
Fatal error: A noreturn function must not return in %s on line %d

Zend/zend_compile.c

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ static const struct reserved_class_name reserved_class_names[] = {
174174
{ZEND_STRL("string")},
175175
{ZEND_STRL("true")},
176176
{ZEND_STRL("void")},
177+
{ZEND_STRL("noreturn")},
177178
{ZEND_STRL("iterable")},
178179
{ZEND_STRL("object")},
179180
{ZEND_STRL("mixed")},
@@ -223,6 +224,7 @@ static const builtin_type_info builtin_types[] = {
223224
{ZEND_STRL("string"), IS_STRING},
224225
{ZEND_STRL("bool"), _IS_BOOL},
225226
{ZEND_STRL("void"), IS_VOID},
227+
{ZEND_STRL("noreturn"), IS_NORETURN},
226228
{ZEND_STRL("iterable"), IS_ITERABLE},
227229
{ZEND_STRL("object"), IS_OBJECT},
228230
{ZEND_STRL("mixed"), IS_MIXED},
@@ -2447,6 +2449,18 @@ static void zend_emit_return_type_check(
24472449
return;
24482450
}
24492451

2452+
/* `return` is illegal in a noreturn function */
2453+
if (ZEND_TYPE_CONTAINS_CODE(type, IS_NORETURN)) {
2454+
if (implicit) {
2455+
/* add run-time check in case we do end up returning */
2456+
zend_emit_op(NULL, ZEND_VERIFY_NORETURN_TYPE, expr, NULL);
2457+
} else {
2458+
zend_error_noreturn(E_COMPILE_ERROR, "A noreturn function must not return");
2459+
}
2460+
2461+
return;
2462+
}
2463+
24502464
if (!expr && !implicit) {
24512465
if (ZEND_TYPE_ALLOW_NULL(type)) {
24522466
zend_error_noreturn(E_COMPILE_ERROR,
@@ -6321,6 +6335,10 @@ static zend_type zend_compile_typename(
63216335
zend_error_noreturn(E_COMPILE_ERROR, "Void can only be used as a standalone type");
63226336
}
63236337

6338+
if ((type_mask & MAY_BE_NORETURN) && (ZEND_TYPE_HAS_CLASS(type) || type_mask != MAY_BE_NORETURN)) {
6339+
zend_error_noreturn(E_COMPILE_ERROR, "noreturn can only be used as a standalone type");
6340+
}
6341+
63246342
if ((type_mask & (MAY_BE_NULL|MAY_BE_FALSE))
63256343
&& !ZEND_TYPE_HAS_CLASS(type) && !(type_mask & ~(MAY_BE_NULL|MAY_BE_FALSE))) {
63266344
if (type_mask == MAY_BE_NULL) {
@@ -6568,6 +6586,10 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall
65686586
zend_error_noreturn(E_COMPILE_ERROR, "void cannot be used as a parameter type");
65696587
}
65706588

6589+
if (ZEND_TYPE_FULL_MASK(arg_info->type) & MAY_BE_NORETURN) {
6590+
zend_error_noreturn(E_COMPILE_ERROR, "noreturn cannot be used as a parameter type");
6591+
}
6592+
65716593
if (default_type != IS_UNDEF && default_type != IS_CONSTANT_AST && !force_nullable
65726594
&& !zend_is_valid_default_value(arg_info->type, &default_node.u.constant)) {
65736595
zend_string *type_str = zend_type_to_string(arg_info->type);
@@ -7159,7 +7181,7 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, z
71597181
if (type_ast) {
71607182
type = zend_compile_typename(type_ast, /* force_allow_null */ 0);
71617183

7162-
if (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_VOID|MAY_BE_CALLABLE)) {
7184+
if (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_VOID|MAY_BE_NORETURN|MAY_BE_CALLABLE)) {
71637185
zend_string *str = zend_type_to_string(type);
71647186
zend_error_noreturn(E_COMPILE_ERROR,
71657187
"Property %s::$%s cannot have type %s",

Zend/zend_execute.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,6 +1232,22 @@ ZEND_API ZEND_COLD void zend_verify_return_error(const zend_function *zf, zval *
12321232
zend_string_release(need_msg);
12331233
}
12341234

1235+
ZEND_API ZEND_COLD void zend_verify_noreturn_error(const zend_function *zf)
1236+
{
1237+
const zend_arg_info *arg_info = &zf->common.arg_info[-1];
1238+
const char *fname, *fsep, *fclass;
1239+
zend_string *need_msg;
1240+
const char *given_msg;
1241+
1242+
zend_verify_type_error_common(
1243+
zf, arg_info, NULL, &fname, &fsep, &fclass, &need_msg, &given_msg);
1244+
1245+
zend_type_error("%s%s%s(): Nothing was expected to be returned",
1246+
fclass, fsep, fname);
1247+
1248+
zend_string_release(need_msg);
1249+
}
1250+
12351251
#if ZEND_DEBUG
12361252
static ZEND_COLD void zend_verify_internal_return_error(const zend_function *zf, zval *value)
12371253
{

Zend/zend_execute.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ ZEND_API ZEND_COLD void zend_verify_arg_error(
7373
const zend_function *zf, const zend_arg_info *arg_info, int arg_num, zval *value);
7474
ZEND_API ZEND_COLD void zend_verify_return_error(
7575
const zend_function *zf, zval *value);
76+
ZEND_API ZEND_COLD void zend_verify_noreturn_error(
77+
const zend_function *zf);
7678
ZEND_API bool zend_verify_ref_array_assignable(zend_reference *ref);
7779
ZEND_API bool zend_value_instanceof_static(zval *zv);
7880

Zend/zend_type_info.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#define MAY_BE_CALLABLE (1 << IS_CALLABLE)
4141
#define MAY_BE_ITERABLE (1 << IS_ITERABLE)
4242
#define MAY_BE_VOID (1 << IS_VOID)
43+
#define MAY_BE_NORETURN (1 << IS_NORETURN)
4344
#define MAY_BE_STATIC (1 << IS_STATIC)
4445

4546
#define MAY_BE_ARRAY_SHIFT (IS_REFERENCE)

Zend/zend_types.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,7 @@ struct _zend_ast_ref {
550550
#define IS_VOID 14
551551
#define IS_STATIC 15
552552
#define IS_MIXED 16
553+
#define IS_NORETURN 17
553554

554555
/* internal types */
555556
#define IS_INDIRECT 12
@@ -558,8 +559,8 @@ struct _zend_ast_ref {
558559
#define _IS_ERROR 15
559560

560561
/* used for casts */
561-
#define _IS_BOOL 17
562-
#define _IS_NUMBER 18
562+
#define _IS_BOOL 18
563+
#define _IS_NUMBER 19
563564

564565
static zend_always_inline zend_uchar zval_get_type(const zval* pz) {
565566
return pz->u1.v.type;

Zend/zend_vm_def.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4237,6 +4237,15 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV
42374237
}
42384238
}
42394239

4240+
ZEND_VM_COLD_CONST_HANDLER(200, ZEND_VERIFY_NORETURN_TYPE, CONST|TMP|VAR|UNUSED|CV, UNUSED|CACHE_SLOT)
4241+
{
4242+
if (OP1_TYPE != IS_UNUSED) {
4243+
SAVE_OPLINE();
4244+
zend_verify_noreturn_error(EX(func));
4245+
HANDLE_EXCEPTION();
4246+
}
4247+
}
4248+
42404249
ZEND_VM_INLINE_HANDLER(62, ZEND_RETURN, CONST|TMP|VAR|CV, ANY, SPEC(OBSERVER))
42414250
{
42424251
USE_OPLINE

0 commit comments

Comments
 (0)