Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support never types #6761

Closed
wants to merge 26 commits into from
Closed

Support never types #6761

wants to merge 26 commits into from

Conversation

muglug
Copy link
Contributor

@muglug muglug commented Mar 8, 2021

RFC

This provides a draft implementation of a never type, useful as a return type for functions that only ever throw or exit.

Zend/tests/return_types/noreturn_disallowed3.phpt Outdated Show resolved Hide resolved
Zend/tests/return_types/noreturn_covariance.phpt Outdated Show resolved Hide resolved
Zend/tests/return_types/noreturn_disallowed3.phpt Outdated Show resolved Hide resolved
Zend/tests/return_types/noreturn_no_variance.phpt Outdated Show resolved Hide resolved
Zend/tests/return_types/noreturn_no_variance.phpt Outdated Show resolved Hide resolved
Zend/zend_compile.c Outdated Show resolved Hide resolved
Zend/zend_inheritance.c Outdated Show resolved Hide resolved
Zend/zend_inheritance.c Outdated Show resolved Hide resolved
@iluuu1994
Copy link
Member

You might also wanna add a terminating test (with exit) and a generator.

@iluuu1994
Copy link
Member

Another small test you could add:

--TEST--
noreturn in reflection
--FILE--
<?php

function foo(): noreturn {}

$noreturnType = (new ReflectionFunction('foo'))->getReturnType();

echo $noreturnType->getName() . "\n";
var_dump($noreturnType->isBuiltin());

?>
--EXPECT--
noreturn
bool(true)

Here's the fix for opcache:

Subject: [PATCH] Fix opcache for noreturn

---
 Zend/Optimizer/dce.c      | 1 +
 Zend/Optimizer/pass1.c    | 1 +
 Zend/Optimizer/zend_cfg.c | 2 ++
 3 files changed, 4 insertions(+)

diff --git a/Zend/Optimizer/dce.c b/Zend/Optimizer/dce.c
index 940f1b6ee2..85b32a7e10 100644
--- a/Zend/Optimizer/dce.c
+++ b/Zend/Optimizer/dce.c
@@ -155,6 +155,7 @@ static inline bool may_have_side_effects(
                case ZEND_TICKS:
                case ZEND_YIELD:
                case ZEND_YIELD_FROM:
+               case ZEND_VERIFY_NORETURN_TYPE:
                        /* Intrinsic side effects */
                        return 1;
                case ZEND_DO_FCALL:
diff --git a/Zend/Optimizer/pass1.c b/Zend/Optimizer/pass1.c
index 86774afef4..fc42002813 100644
--- a/Zend/Optimizer/pass1.c
+++ b/Zend/Optimizer/pass1.c
@@ -677,6 +677,7 @@ constant_binary_op:
                case ZEND_COALESCE:
                case ZEND_ASSERT_CHECK:
                case ZEND_JMP_NULL:
+               case ZEND_VERIFY_NORETURN_TYPE:
                        collect_constants = 0;
                        break;
                }
diff --git a/Zend/Optimizer/zend_cfg.c b/Zend/Optimizer/zend_cfg.c
index 973a93ef6c..9d62563693 100644
--- a/Zend/Optimizer/zend_cfg.c
+++ b/Zend/Optimizer/zend_cfg.c
@@ -301,6 +301,7 @@ ZEND_API int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, u
                        case ZEND_GENERATOR_RETURN:
                        case ZEND_EXIT:
                        case ZEND_MATCH_ERROR:
+                       case ZEND_VERIFY_NORETURN_TYPE:
                                if (i + 1 < op_array->last) {
                                        BB_START(i + 1);
                                }
@@ -514,6 +515,7 @@ ZEND_API int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, u
                        case ZEND_EXIT:
                        case ZEND_THROW:
                        case ZEND_MATCH_ERROR:
+                       case ZEND_VERIFY_NORETURN_TYPE:
                                break;
                        case ZEND_JMP:
                                block->successors_count = 1;
--

I'm far from an opcache expert, there might be more things missing. We'll have to ask someone more experienced.

@muglug muglug marked this pull request as ready for review March 10, 2021 17:37
@tuqqu
Copy link

tuqqu commented Mar 10, 2021

Can a function that loops forever be marked as noreturn?
Hacklang, TypeScript and Rust (with the ! return type) support this.

function loop(): noreturn {
    while (true) {}
}

@iluuu1994
Copy link
Member

iluuu1994 commented Mar 10, 2021

@tuqqu Yep, that should work fine. Should be specified in the RFC though. Hard to test in a test 😜


try {
foo();
} catch (Exception $e) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and below: exception variable can be omitted now.

foo();
?>
--EXPECTF--
Fatal error: Uncaught TypeError: foo(): Nothing was expected to be returned in %s:%d
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hope you don't mind my suggestions:

  • Mention noreturn explicitly
  • Perhaps instead of "Nothing was expected to be returned" something like "Attempt to return from a noreturn function" should be clearer?
  • It's not a type error, but rather a logic one. The noreturn "type" is merely how functions are marked as never returning.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps instead of "Nothing was expected to be returned" something like "Attempt to return from a noreturn function" should be clearer?

This error is printed when the function ends without returning. So maybe something like "noreturn function was expected to throw, terminate or run infinitely". That's very explicit, maybe somebody has a better idea.

The noreturn "type" is merely how functions are marked as never returning.

noreturn is an actual type in PHPs type system. We could add a different error but I wouldn't say TypeError is wrong.

Zend/zend_inheritance.c Outdated Show resolved Hide resolved

public function bar(): noreturn
{
throw new UnexpectedValueException('child');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add an inheritance test for a static method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually meant one where noreturn is returned from a static method 🙂 I wasn't sure if there might be some issues with the == equality type check.

Comment on lines 1242 to 1243
zend_verify_type_error_common(
zf, arg_info, NULL, &fname, &fsep, &fclass, &need_msg, &given_msg);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the reason for this check?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As that's the only part you need, you can use get_function_or_method_name(zf) to fetch the function/method prefix for the error.

@@ -1232,6 +1232,22 @@ ZEND_API ZEND_COLD void zend_verify_return_error(const zend_function *zf, zval *
zend_string_release(need_msg);
}

ZEND_API ZEND_COLD void zend_verify_noreturn_error(const zend_function *zf)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rename this into zend_noreturn_error() it always emits error.

@@ -4237,6 +4237,13 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV
}
}

ZEND_VM_COLD_CONST_HANDLER(201, ZEND_VERIFY_NORETURN_TYPE, UNUSED, UNUSED)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rename this into ZEND_NORETURN or ZEND_NORETURN_ERROR.

Zend/zend_types.h Outdated Show resolved Hide resolved
Zend/Optimizer/zend_dump.c Outdated Show resolved Hide resolved
Zend/zend_inheritance.c Outdated Show resolved Hide resolved
Zend/zend_execute.h Outdated Show resolved Hide resolved
@muglug muglug changed the title Support noreturn types Support never types Apr 14, 2021
Copy link
Member

@nikic nikic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fine. I'm missing a test for generator with never return type.

Zend/zend_compile.c Outdated Show resolved Hide resolved
Zend/zend_compile.c Outdated Show resolved Hide resolved
Zend/zend_compile.c Outdated Show resolved Hide resolved
Comment on lines 1242 to 1243
zend_verify_type_error_common(
zf, arg_info, NULL, &fname, &fsep, &fclass, &need_msg, &given_msg);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As that's the only part you need, you can use get_function_or_method_name(zf) to fetch the function/method prefix for the error.

Zend/zend_vm_def.h Outdated Show resolved Hide resolved
ext/tokenizer/tokenizer_data.c Outdated Show resolved Hide resolved
@muglug
Copy link
Contributor Author

muglug commented Apr 16, 2021

@nikic I've added the test for a generator function returning never

@@ -16,7 +16,7 @@

/*
DO NOT EDIT THIS FILE!
This file is generated using tokenizer_data_gen.php
This file is generated using tokenizer_data_gen.sh
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commits in this PR seems like they should be rebased and squashed - the changes to tokenizer_data.c ended up being unnecessary?

@nikic nikic closed this in 6cd0b48 Apr 19, 2021
@muglug muglug deleted the support-noreturn branch April 19, 2021 12:29
jrfnl added a commit to PHPCompatibility/PHPCompatibility that referenced this pull request Dec 30, 2022
> **Never type**
>
> A new`return only type `never` has been added. This indicates that a function either `exit()`, throws an exception, or doesn't terminate.

Includes unit tests.

Refs:
* https://wiki.php.net/rfc/noreturn_type
* https://www.php.net/manual/en/migration81.new-features.php#migration81.new-features.core.never-type
* php/php-src#6761
* php/php-src@6cd0b48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.