Skip to content

Commit 6cd0b48

Browse files
muglugnikic
authored andcommitted
Implement never return type
The never type can be used to indicate that a function never returns, for example because it always unwinds. RFC: https://wiki.php.net/rfc/noreturn_type Closes phpGH-6761.
1 parent 6adfe9d commit 6cd0b48

32 files changed

+888
-509
lines changed

UPGRADING

+2
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ PHP 8.1 UPGRADE NOTES
151151
RFC: https://wiki.php.net/rfc/array_unpacking_string_keys
152152
. Added support for enumerations.
153153
RFC: https://wiki.php.net/rfc/enumerations
154+
. Added support for never return type
155+
RFC: https://wiki.php.net/rfc/noreturn_type
154156

155157
- Curl:
156158
. Added CURLOPT_DOH_URL option.

Zend/Optimizer/dce.c

+1
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ static inline bool may_have_side_effects(
155155
case ZEND_TICKS:
156156
case ZEND_YIELD:
157157
case ZEND_YIELD_FROM:
158+
case ZEND_VERIFY_NEVER_TYPE:
158159
/* Intrinsic side effects */
159160
return 1;
160161
case ZEND_DO_FCALL:

Zend/Optimizer/pass1.c

+1
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,7 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
676676
case ZEND_COALESCE:
677677
case ZEND_ASSERT_CHECK:
678678
case ZEND_JMP_NULL:
679+
case ZEND_VERIFY_NEVER_TYPE:
679680
collect_constants = 0;
680681
break;
681682
}

Zend/Optimizer/zend_cfg.c

+2
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ ZEND_API int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, u
301301
case ZEND_GENERATOR_RETURN:
302302
case ZEND_EXIT:
303303
case ZEND_MATCH_ERROR:
304+
case ZEND_VERIFY_NEVER_TYPE:
304305
if (i + 1 < op_array->last) {
305306
BB_START(i + 1);
306307
}
@@ -515,6 +516,7 @@ ZEND_API int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, u
515516
case ZEND_EXIT:
516517
case ZEND_THROW:
517518
case ZEND_MATCH_ERROR:
519+
case ZEND_VERIFY_NEVER_TYPE:
518520
break;
519521
case ZEND_JMP:
520522
block->successors_count = 1;

Zend/Optimizer/zend_dump.c

+3
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_NEVER:
475+
fprintf(stderr, " (never)");
476+
break;
474477
default:
475478
fprintf(stderr, " (\?\?\?)");
476479
break;
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
never return type: acceptable cases
3+
--FILE--
4+
<?php
5+
6+
function foo(): never {
7+
throw new Exception('bad');
8+
}
9+
10+
try {
11+
foo();
12+
} catch (Exception $e) {
13+
// do nothing
14+
}
15+
16+
function calls_foo(): never {
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!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
--TEST--
2+
never return type: acceptable covariance cases
3+
--FILE--
4+
<?php
5+
6+
class A
7+
{
8+
public function foo(): string
9+
{
10+
return "hello";
11+
}
12+
13+
public function bar(): never
14+
{
15+
throw new UnexpectedValueException('parent');
16+
}
17+
18+
public function &baz()
19+
{
20+
}
21+
22+
public function someReturningStaticMethod() : static
23+
{
24+
}
25+
}
26+
27+
class B extends A
28+
{
29+
public function foo(): never
30+
{
31+
throw new UnexpectedValueException('bad');
32+
}
33+
34+
public function bar(): never
35+
{
36+
throw new UnexpectedValueException('child');
37+
}
38+
39+
public function &baz(): never
40+
{
41+
throw new UnexpectedValueException('child');
42+
}
43+
44+
public function someReturningStaticMethod(): never
45+
{
46+
throw new UnexpectedValueException('child');
47+
}
48+
}
49+
50+
try {
51+
(new B)->foo();
52+
} catch (UnexpectedValueException $e) {
53+
// do nothing
54+
}
55+
56+
try {
57+
(new B)->bar();
58+
} catch (UnexpectedValueException $e) {
59+
// do nothing
60+
}
61+
62+
try {
63+
(new B)->baz();
64+
} catch (UnexpectedValueException $e) {
65+
// do nothing
66+
}
67+
68+
try {
69+
(new B)->someReturningStaticMethod();
70+
} catch (UnexpectedValueException $e) {
71+
// do nothing
72+
}
73+
74+
echo "OK!", PHP_EOL;
75+
76+
?>
77+
--EXPECT--
78+
OK!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
never return type: unacceptable cases: any return
3+
--FILE--
4+
<?php
5+
6+
function foo(): never {
7+
return "hello"; // not permitted in a never function
8+
}
9+
10+
// Note the lack of function call: function validated at compile-time
11+
?>
12+
--EXPECTF--
13+
Fatal error: A never-returning function must not return in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
never return type: unacceptable cases: empty return
3+
--FILE--
4+
<?php
5+
6+
function foo(): never {
7+
return; // not permitted in a never function
8+
}
9+
10+
// Note the lack of function call: function validated at compile-time
11+
?>
12+
--EXPECTF--
13+
Fatal error: A never-returning function must not return in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
never return type: unacceptable cases: implicit return
3+
--FILE--
4+
<?php
5+
6+
function foo(): never {
7+
if (false) {
8+
throw new Exception('bad');
9+
}
10+
}
11+
12+
foo();
13+
?>
14+
--EXPECTF--
15+
Fatal error: Uncaught TypeError: foo(): never-returning function must not implicitly return in %s:%d
16+
Stack trace:
17+
#0 %s(%d): foo()
18+
#1 {main}
19+
thrown in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
never return type: never cannot return from finally
3+
--FILE--
4+
<?php
5+
6+
function foo() : never {
7+
try {
8+
throw new Exception('bad');
9+
} finally {
10+
return;
11+
}
12+
}
13+
14+
// Note the lack of function call: function validated at compile-time
15+
?>
16+
--EXPECTF--
17+
Fatal error: A never-returning function must not return in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
never return type: not valid as a parameter type
3+
--FILE--
4+
<?php
5+
6+
function generateList(string $uri): never {
7+
yield 1;
8+
exit();
9+
}
10+
?>
11+
--EXPECTF--
12+
Fatal error: Generator return type must be a supertype of Generator, never given in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
never return type: prevent unacceptable cases
3+
--FILE--
4+
<?php
5+
6+
class A
7+
{
8+
public function bar(): never
9+
{
10+
throw new \Exception('parent');
11+
}
12+
}
13+
14+
class B extends A
15+
{
16+
public function bar(): string
17+
{
18+
return "hello";
19+
}
20+
}
21+
22+
(new B)->bar();
23+
24+
?>
25+
--EXPECTF--
26+
Fatal error: Declaration of B::bar(): string must be compatible with A::bar(): never in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
--TEST--
2+
never return type: not valid as a parameter type
3+
--FILE--
4+
<?php
5+
6+
function foobar(never $a) {}
7+
?>
8+
--EXPECTF--
9+
Fatal error: never cannot be used as a parameter type in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
never in reflection
3+
--FILE--
4+
<?php
5+
6+
function foo(): never {}
7+
8+
$neverType = (new ReflectionFunction('foo'))->getReturnType();
9+
10+
echo $neverType->getName() . "\n";
11+
var_dump($neverType->isBuiltin());
12+
13+
?>
14+
--EXPECT--
15+
never
16+
bool(true)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
never return type: never cannot return from throw expression
3+
--FILE--
4+
<?php
5+
6+
function foo() : never {
7+
return throw new Exception('bad');
8+
}
9+
10+
// Note the lack of function call: function validated at compile-time
11+
?>
12+
--EXPECTF--
13+
Fatal error: A never-returning function must not return in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
never type of __toString method
3+
--FILE--
4+
<?php
5+
6+
class A implements Stringable {
7+
public function __toString(): string {
8+
return "hello";
9+
}
10+
}
11+
12+
class B extends A {
13+
public function __toString(): never {
14+
throw new \Exception('not supported');
15+
}
16+
}
17+
18+
try {
19+
echo (string) (new B());
20+
} catch (Exception $e) {
21+
// do nothing
22+
}
23+
24+
echo "done";
25+
26+
?>
27+
--EXPECT--
28+
done
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Test typed properties disallow never
3+
--FILE--
4+
<?php
5+
class Foo {
6+
public never $int;
7+
}
8+
9+
$foo = new Foo();
10+
?>
11+
--EXPECTF--
12+
Fatal error: Property Foo::$int cannot have type never in %s on line 3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Combining never with class type
3+
--FILE--
4+
<?php
5+
6+
function test(): T|never {}
7+
8+
?>
9+
--EXPECTF--
10+
Fatal error: never can only be used as a standalone type in %s on line %d

0 commit comments

Comments
 (0)