Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions compiler/code-gen/files/function-header.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))";
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/code-gen/vertex-compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions compiler/debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down
18 changes: 18 additions & 0 deletions compiler/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,8 @@ VertexAdaptor<op_func_param> 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
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -1574,6 +1577,11 @@ VertexAdaptor<op_function> 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<TypeHintPrimitive>()) {
cur_function->is_no_return = type->ptype == tp_never;
}
}

if (is_arrow) {
Expand Down Expand Up @@ -1991,6 +1999,16 @@ 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<TypeHintPrimitive>()) {
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<op_catch> GenTree::get_catch() {
CE (expect(tok_catch, "'catch'"));
CE (expect(tok_oppar, "'('"));
Expand Down
1 change: 1 addition & 0 deletions compiler/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions compiler/inferring/primitive-type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
1 change: 1 addition & 0 deletions compiler/inferring/primitive-type.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ enum PrimitiveType {
tp_array,
tp_mixed,
tp_void,
tp_never,
tp_tuple,
tp_shape,
tp_future,
Expand Down
9 changes: 9 additions & 0 deletions compiler/inferring/type-data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions compiler/keywords.gperf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion compiler/phpdoc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ const std::map<string, php_doc_tag::doc_type> 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},
Expand Down Expand Up @@ -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());
Expand Down
1 change: 0 additions & 1 deletion compiler/phpdoc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion compiler/pipes/collect-main-edges.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 1 addition & 4 deletions compiler/pipes/final-check.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -382,10 +382,7 @@ VertexPtr FinalCheckPass::on_enter_vertex(VertexPtr vertex) {
check_op_func_call(vertex.as<op_func_call>());
}
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<op_instance_prop>()->instance());
Expand Down
14 changes: 12 additions & 2 deletions compiler/pipes/fix-returns.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<op_func_call>()) {
Expand All @@ -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<op_func_call>()) {
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<op_func_call>()) {
FunctionPtr f = call->func_id;
Expand Down
5 changes: 0 additions & 5 deletions compiler/pipes/parse-and-apply-phpdoc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions compiler/token.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ enum TokenType {
tok_callable,
tok_bool,
tok_void,
tok_never,
tok_mixed,

tok_conv_int,
Expand Down
4 changes: 0 additions & 4 deletions docs/kphp-language/kphp-vs-php/phpdoc-annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<aside>@kphp-no-return</aside>

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.

<aside>@kphp-pure-function</aside>

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.
Expand Down
9 changes: 3 additions & 6 deletions functions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down
25 changes: 25 additions & 0 deletions tests/phpt/php8/never_type/001_basic.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@ok php8
<?php

function foo(): never {
throw new Exception('bad');
}

try {
foo();
} catch (Exception $e) {
// do nothing
}

function calls_foo(): never {
foo();
}

try {
calls_foo();
} catch (Exception $e) {
// do nothing
}

echo "OK!", PHP_EOL;

50 changes: 50 additions & 0 deletions tests/phpt/php8/never_type/002_covariance.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@ok php8
<?php

class A {
public function foo(): string {
return "hello";
}

public function bar(): never {
throw new UnexpectedValueException('parent');
}

public function someReturningStaticMethod(): static {
return $this;
}
}

class B extends A {
public function foo(): never {
throw new UnexpectedValueException('bad');
}

public function bar(): never {
throw new UnexpectedValueException('child');
}

public function someReturningStaticMethod(): never {
throw new UnexpectedValueException('child');
}
}

try {
(new B)->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;
10 changes: 10 additions & 0 deletions tests/phpt/php8/never_type/003_explicit_return.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@kphp_should_fail php8
/return string from foo/
/but it's declared as @return never/
<?php

function foo(): never {
return "hello"; // not permitted in a never function
}

foo();
10 changes: 10 additions & 0 deletions tests/phpt/php8/never_type/004_explicit_empty_return.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@kphp_should_fail php8
/return void from foo/
/but it's declared as @return never/
<?php

function foo(): never {
return; // not permitted in a never function
}

foo();
12 changes: 12 additions & 0 deletions tests/phpt/php8/never_type/005_implicit_return.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@kphp_should_fail php8
/return void from foo/
/but it's declared as @return never/
<?php

function foo(): never {
if (false) {
throw new Exception('bad');
}
}

foo();
17 changes: 17 additions & 0 deletions tests/phpt/php8/never_type/006_covariant_fail.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@kphp_should_fail php8
/Declaration of B::bar\(\) must be compatible with A::bar\(\)/
<?php

class A {
public function bar(): never {
throw new \Exception('parent');
}
}

class B extends A {
public function bar(): string {
return "hello";
}
}

(new B)->bar();
5 changes: 5 additions & 0 deletions tests/phpt/php8/never_type/007_never_as_param_type.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@kphp_should_fail php8
/never cannot be used as a parameter type/
<?php

function foobar(never $a) {}
Loading