From 757ca9d221181fb41793169d31f9271850028961 Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Fri, 15 Oct 2021 20:39:41 +0300 Subject: [PATCH 1/2] added support for never type --- compiler/code-gen/files/function-header.cpp | 3 -- compiler/code-gen/vertex-compiler.cpp | 2 +- compiler/debug.cpp | 1 + compiler/gentree.cpp | 15 ++++++ compiler/gentree.h | 1 + compiler/inferring/primitive-type.cpp | 1 + compiler/inferring/primitive-type.h | 1 + compiler/inferring/type-data.cpp | 9 ++++ compiler/keywords.gperf | 1 + compiler/phpdoc.cpp | 4 +- compiler/phpdoc.h | 1 - compiler/pipes/collect-main-edges.cpp | 2 +- compiler/pipes/final-check.cpp | 5 +- compiler/pipes/fix-returns.cpp | 14 +++++- compiler/pipes/parse-and-apply-phpdoc.cpp | 5 -- compiler/token.h | 1 + .../kphp-vs-php/phpdoc-annotations.md | 4 -- functions.txt | 9 ++-- tests/phpt/php8/never_type/001_basic.php | 25 ++++++++++ tests/phpt/php8/never_type/002_covariance.php | 50 +++++++++++++++++++ .../php8/never_type/003_explicit_return.php | 10 ++++ .../never_type/004_explicit_empty_return.php | 10 ++++ .../php8/never_type/005_implicit_return.php | 12 +++++ .../php8/never_type/006_covariant_fail.php | 17 +++++++ .../never_type/007_never_as_param_type.php | 5 ++ .../never_type/008_never_as_prop_type.php | 9 ++++ .../never_type/009_never_for_to_string.php | 23 +++++++++ tests/phpt/pk/026_no_return.php | 22 -------- tests/phpt/pk/027_no_return_return.php | 25 ---------- tests/phpt/pk/028_no_return_throws.php | 24 --------- tests/python/lib/file_utils.py | 2 +- 31 files changed, 213 insertions(+), 100 deletions(-) create mode 100644 tests/phpt/php8/never_type/001_basic.php create mode 100644 tests/phpt/php8/never_type/002_covariance.php create mode 100644 tests/phpt/php8/never_type/003_explicit_return.php create mode 100644 tests/phpt/php8/never_type/004_explicit_empty_return.php create mode 100644 tests/phpt/php8/never_type/005_implicit_return.php create mode 100644 tests/phpt/php8/never_type/006_covariant_fail.php create mode 100644 tests/phpt/php8/never_type/007_never_as_param_type.php create mode 100644 tests/phpt/php8/never_type/008_never_as_prop_type.php create mode 100644 tests/phpt/php8/never_type/009_never_for_to_string.php delete mode 100644 tests/phpt/pk/026_no_return.php delete mode 100644 tests/phpt/pk/027_no_return_return.php delete mode 100644 tests/phpt/pk/028_no_return_throws.php diff --git a/compiler/code-gen/files/function-header.cpp b/compiler/code-gen/files/function-header.cpp index edabe8cde5..d5a952495a 100644 --- a/compiler/code-gen/files/function-header.cpp +++ b/compiler/code-gen/files/function-header.cpp @@ -33,9 +33,6 @@ void FunctionH::compile(CodeGenerator &W) const { W << "inline "; } W << FunctionDeclaration(function, true); - if (function->is_no_return) { - W << " __attribute__((noreturn))"; - } if (function->is_flatten) { W << " __attribute__((flatten))"; } diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index d87773d253..2a6b4a5e63 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -138,7 +138,7 @@ struct EmptyReturn { } else { W << "return "; } - if (context.inside_null_coalesce_fallback || tp->ptype() != tp_void) { + if (context.inside_null_coalesce_fallback || (tp->ptype() != tp_void && tp->ptype() != tp_never)) { W << "{}"; } if (context.resumable_flag) { diff --git a/compiler/debug.cpp b/compiler/debug.cpp index 0164a5f4d3..9be386f420 100644 --- a/compiler/debug.cpp +++ b/compiler/debug.cpp @@ -162,6 +162,7 @@ std::string debugTokenName(TokenType t) { {tok_callable, "tok_callable"}, {tok_bool, "tok_bool"}, {tok_void, "tok_void"}, + {tok_never, "tok_never"}, {tok_mixed, "tok_mixed"}, {tok_conv_int, "tok_conv_int"}, {tok_conv_float, "tok_conv_float"}, diff --git a/compiler/gentree.cpp b/compiler/gentree.cpp index adb43bc437..62f56316be 100644 --- a/compiler/gentree.cpp +++ b/compiler/gentree.cpp @@ -876,6 +876,8 @@ VertexAdaptor GenTree::get_func_param() { auto location = auto_location(); const TypeHint *type_hint = get_typehint(); + on_void_never_type_error(type_hint, "parameter"); + bool is_varg = false; // if the argument is vararg and has a type hint — e.g. int ...$a — then cur points to $a, as ... were consumed by the type lexer @@ -1475,6 +1477,7 @@ VertexPtr GenTree::get_class_member(vk::string_view phpdoc_str) { } const TypeHint *type_hint = get_typehint(); + on_void_never_type_error(type_hint, "class property"); if (test_expect(tok_var_name)) { kphp_error(!cur_class->is_interface(), "Interfaces may not include member variables"); @@ -1574,6 +1577,11 @@ VertexAdaptor GenTree::get_function(TokenType tok, vk::string_view next_cur(); cur_function->return_typehint = get_typehint(); kphp_error(cur_function->return_typehint, "Expected return typehint after :"); + + // if explicit never typehint + if (const auto type = cur_function->return_typehint->try_as()) { + cur_function->is_no_return = type->ptype == tp_never; + } } if (is_arrow) { @@ -1991,6 +1999,13 @@ const TypeHint *GenTree::get_typehint() { } } +void GenTree::on_void_never_type_error(const TypeHint *type_hint, std::string place) { + if (const auto type = type_hint->try_as()) { + kphp_error(type->ptype != tp_void, fmt_format("void cannot be used as a {} type", place)); + kphp_error(type->ptype != tp_never, fmt_format("never cannot be used as a {} type", place)); + } +} + VertexAdaptor GenTree::get_catch() { CE (expect(tok_catch, "'catch'")); CE (expect(tok_oppar, "'('")); diff --git a/compiler/gentree.h b/compiler/gentree.h index 31fe78c097..23dcc4b1f9 100644 --- a/compiler/gentree.h +++ b/compiler/gentree.h @@ -152,6 +152,7 @@ class GenTree { private: const TypeHint *get_typehint(); + static void on_void_never_type_error(const TypeHint *type_hint, std::string place); static void check_and_remove_num_separators(std::string &s); VertexPtr get_op_num_const(); diff --git a/compiler/inferring/primitive-type.cpp b/compiler/inferring/primitive-type.cpp index f10507a6db..668acd5f76 100644 --- a/compiler/inferring/primitive-type.cpp +++ b/compiler/inferring/primitive-type.cpp @@ -29,6 +29,7 @@ const char *ptype_name(PrimitiveType id) { case tp_regexp: return "regexp"; case tp_Class: return "Class"; case tp_void: return "void"; + case tp_never: return "never"; case tp_Error: return "Error"; case ptype_size: kphp_fail(); } diff --git a/compiler/inferring/primitive-type.h b/compiler/inferring/primitive-type.h index 7fe4f33092..3ccd84a9b4 100644 --- a/compiler/inferring/primitive-type.h +++ b/compiler/inferring/primitive-type.h @@ -17,6 +17,7 @@ enum PrimitiveType { tp_array, tp_mixed, tp_void, + tp_never, tp_tuple, tp_shape, tp_future, diff --git a/compiler/inferring/type-data.cpp b/compiler/inferring/type-data.cpp index 9ee59b29fe..9a2b763c7e 100644 --- a/compiler/inferring/type-data.cpp +++ b/compiler/inferring/type-data.cpp @@ -506,6 +506,10 @@ inline void get_cpp_style_type(const TypeData *type, std::string &res) { res += "Unknown"; break; } + case tp_never: { + res += "void"; + break; + } default : { res += ptype_name(tp); break; @@ -676,6 +680,8 @@ int type_strlen(const TypeData *type) { return STRLEN_CLASS; case tp_void: return STRLEN_VOID; + case tp_never: + return STRLEN_VOID; case tp_future: return STRLEN_FUTURE; case tp_future_queue: @@ -773,6 +779,9 @@ bool is_less_or_equal_type(const TypeData *given, const TypeData *expected, cons case tp_float: case tp_bool: case tp_void: + if (tp == tp_never) { + return true; + } if (tp == expected->ptype()) { return true; } diff --git a/compiler/keywords.gperf b/compiler/keywords.gperf index 2e48d8470a..ddaa40842d 100644 --- a/compiler/keywords.gperf +++ b/compiler/keywords.gperf @@ -66,6 +66,7 @@ string, tok_string object, tok_object callable, tok_callable void, tok_void +never, tok_never mixed, tok_mixed bool, tok_bool false, tok_false diff --git a/compiler/phpdoc.cpp b/compiler/phpdoc.cpp index a01a8ac1db..d029e483ac 100644 --- a/compiler/phpdoc.cpp +++ b/compiler/phpdoc.cpp @@ -45,7 +45,6 @@ const std::map php_doc_tag::str2doc_type = { {"@kphp-immutable-class", kphp_immutable_class}, {"@kphp-tl-class", kphp_tl_class}, {"@kphp-const", kphp_const}, - {"@kphp-no-return", kphp_noreturn}, {"@kphp-warn-unused-result", kphp_warn_unused_result}, {"@kphp-warn-performance", kphp_warn_performance}, {"@kphp-analyze-performance", kphp_analyze_performance}, @@ -191,6 +190,9 @@ const TypeHint *PhpDocTypeRuleParser::parse_simple_type() { case tok_void: cur_tok++; return TypeHintPrimitive::create(tp_void); + case tok_never: + cur_tok++; + return TypeHintPrimitive::create(tp_never); case tok_tuple: cur_tok++; return TypeHintTuple::create(parse_nested_type_hints()); diff --git a/compiler/phpdoc.h b/compiler/phpdoc.h index aef6fe03c6..4ec9ea0a5f 100644 --- a/compiler/phpdoc.h +++ b/compiler/phpdoc.h @@ -35,7 +35,6 @@ struct php_doc_tag { kphp_immutable_class, kphp_tl_class, kphp_const, - kphp_noreturn, kphp_warn_unused_result, kphp_warn_performance, kphp_analyze_performance, diff --git a/compiler/pipes/collect-main-edges.cpp b/compiler/pipes/collect-main-edges.cpp index 82cb7b1587..ffa07790e3 100644 --- a/compiler/pipes/collect-main-edges.cpp +++ b/compiler/pipes/collect-main-edges.cpp @@ -549,7 +549,7 @@ void CollectMainEdgesPass::on_finish() { } if (!have_returns) { // hack to work well with functions which always throws - create_type_assign(as_lvalue(current_function, -1), TypeData::get_type(tp_void)); + create_type_assign(as_lvalue(current_function, -1), TypeData::get_type(tp_never)); } call_on_var(current_function->local_var_ids); call_on_var(current_function->global_var_ids); diff --git a/compiler/pipes/final-check.cpp b/compiler/pipes/final-check.cpp index 8455b1480c..876a383904 100644 --- a/compiler/pipes/final-check.cpp +++ b/compiler/pipes/final-check.cpp @@ -382,10 +382,7 @@ VertexPtr FinalCheckPass::on_enter_vertex(VertexPtr vertex) { check_op_func_call(vertex.as()); } if (vertex->type() == op_return && current_function->is_no_return) { - kphp_error(false, "Return is done from no return function"); - } - if (current_function->can_throw() && current_function->is_no_return) { - kphp_error(false, "Exception is thrown from no return function"); + kphp_error(false, "A never-returning function must not return"); } if (vertex->type() == op_instance_prop) { const TypeData *lhs_type = tinf::get_type(vertex.as()->instance()); diff --git a/compiler/pipes/fix-returns.cpp b/compiler/pipes/fix-returns.cpp index 775da535e8..f289da4966 100644 --- a/compiler/pipes/fix-returns.cpp +++ b/compiler/pipes/fix-returns.cpp @@ -8,12 +8,15 @@ #include "compiler/inferring/public.h" VertexPtr FixReturnsPass::on_enter_vertex(VertexPtr root) { - auto is_void_fun = [](FunctionPtr f) { + const auto is_void_fun = [](FunctionPtr f) { return tinf::get_type(f, -1)->ptype() == tp_void; }; - auto is_void_expr = [](VertexPtr root) { + const auto is_void_expr = [](VertexPtr root) { return tinf::get_type(root)->ptype() == tp_void; }; + const auto is_never_expr = [](VertexPtr root) { + return tinf::get_type(root)->ptype() == tp_never; + }; if (root->rl_type == val_r && is_void_expr(root)) { if (auto call = root.try_as()) { @@ -24,6 +27,13 @@ VertexPtr FixReturnsPass::on_enter_vertex(VertexPtr root) { } } + if (root->rl_type == val_r && is_never_expr(root)) { + if (const auto call = root.try_as()) { + const auto fun = call->func_id; + kphp_error(0, fmt_format("Using result of never function {}", fun->get_human_readable_name())); + } + } + if (root->rl_type == val_none) { if (auto call = root.try_as()) { FunctionPtr f = call->func_id; diff --git a/compiler/pipes/parse-and-apply-phpdoc.cpp b/compiler/pipes/parse-and-apply-phpdoc.cpp index f25baeb651..2bffe23428 100644 --- a/compiler/pipes/parse-and-apply-phpdoc.cpp +++ b/compiler/pipes/parse-and-apply-phpdoc.cpp @@ -233,11 +233,6 @@ class ParseAndApplyPhpDocForFunction { break; } - case php_doc_tag::kphp_noreturn: { - f_->is_no_return = true; - break; - } - case php_doc_tag::kphp_lib_export: { f_->kphp_lib_export = true; break; diff --git a/compiler/token.h b/compiler/token.h index fecd6473d6..c2570fa858 100644 --- a/compiler/token.h +++ b/compiler/token.h @@ -153,6 +153,7 @@ enum TokenType { tok_callable, tok_bool, tok_void, + tok_never, tok_mixed, tok_conv_int, diff --git a/docs/kphp-language/kphp-vs-php/phpdoc-annotations.md b/docs/kphp-language/kphp-vs-php/phpdoc-annotations.md index 149e23c793..71a549cd33 100644 --- a/docs/kphp-language/kphp-vs-php/phpdoc-annotations.md +++ b/docs/kphp-language/kphp-vs-php/phpdoc-annotations.md @@ -30,10 +30,6 @@ Marks this function as 'inline' for gcc, and codegen implementation places it in Forces kphp to start compiling this function even if it is not used explicitly. Typically, is required for callbacks passed as strings, as they are resolved much later. - - -Indicates, that this function never returns (always calls *exit()*). While building a control flow graph, KPHP treats all code after such functions' invocations as inaccessible, does not warn on missing break, etc. - Tells KPHP that this function is pure: the result is always the same on constant arguments. Therefore, a function can be called in constant arrays for example. diff --git a/functions.txt b/functions.txt index e79730ade1..429736013b 100644 --- a/functions.txt +++ b/functions.txt @@ -154,12 +154,9 @@ define('E_ALL', 32767); function error_get_last() ::: mixed; function error_reporting ($e ::: int = TODO) ::: int; function warning ($message ::: string) ::: void; -/** @kphp-no-return */ -function critical_error($message ::: string) ::: void; -/** @kphp-no-return */ -function exit($code = 0) ::: void; -/** @kphp-no-return */ -function die($code = 0) ::: void; +function critical_error($message ::: string) ::: never; +function exit($code = 0) ::: never; +function die($code = 0) ::: never; function register_kphp_on_warning_callback(callable(string $warning_message, string[] $stacktrace):void $stacktrace) ::: void; function kphp_set_context_on_error(mixed[] $tags, mixed $extra_info, string $env = "") ::: void; function kphp_backtrace($pretty ::: bool = true) ::: string[]; diff --git a/tests/phpt/php8/never_type/001_basic.php b/tests/phpt/php8/never_type/001_basic.php new file mode 100644 index 0000000000..f7e2cc2afc --- /dev/null +++ b/tests/phpt/php8/never_type/001_basic.php @@ -0,0 +1,25 @@ +@ok php8 +foo(); +} catch (UnexpectedValueException $e) { + // do nothing +} + +try { + (new B)->bar(); +} catch (UnexpectedValueException $e) { + // do nothing +} + +try { + (new B)->someReturningStaticMethod(); +} catch (UnexpectedValueException $e) { + // do nothing +} + +echo "OK!", PHP_EOL; diff --git a/tests/phpt/php8/never_type/003_explicit_return.php b/tests/phpt/php8/never_type/003_explicit_return.php new file mode 100644 index 0000000000..e824158424 --- /dev/null +++ b/tests/phpt/php8/never_type/003_explicit_return.php @@ -0,0 +1,10 @@ +@kphp_should_fail php8 +/return string from foo/ +/but it's declared as @return never/ +bar(); diff --git a/tests/phpt/php8/never_type/007_never_as_param_type.php b/tests/phpt/php8/never_type/007_never_as_param_type.php new file mode 100644 index 0000000000..6a26332b5d --- /dev/null +++ b/tests/phpt/php8/never_type/007_never_as_param_type.php @@ -0,0 +1,5 @@ +@kphp_should_fail php8 +/never cannot be used as a parameter type/ + Date: Sat, 16 Oct 2021 21:00:01 +0300 Subject: [PATCH 2/2] fixed panic --- compiler/gentree.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/gentree.cpp b/compiler/gentree.cpp index 62f56316be..ec1355c40e 100644 --- a/compiler/gentree.cpp +++ b/compiler/gentree.cpp @@ -2000,6 +2000,9 @@ const TypeHint *GenTree::get_typehint() { } void GenTree::on_void_never_type_error(const TypeHint *type_hint, std::string place) { + if (type_hint == nullptr) { + return; + } if (const auto type = type_hint->try_as()) { kphp_error(type->ptype != tp_void, fmt_format("void cannot be used as a {} type", place)); kphp_error(type->ptype != tp_never, fmt_format("never cannot be used as a {} type", place));