summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--UPGRADING3
-rw-r--r--Zend/tests/throw/001.phpt175
-rw-r--r--Zend/tests/throw/002.phpt127
-rw-r--r--Zend/zend_compile.c11
-rw-r--r--Zend/zend_language_parser.y3
-rw-r--r--ext/tokenizer/tests/PhpToken_getAll.phpt16
-rw-r--r--ext/tokenizer/tests/token_get_all_variation4.phpt2
-rw-r--r--ext/tokenizer/tokenizer_data.c4
8 files changed, 325 insertions, 16 deletions
diff --git a/UPGRADING b/UPGRADING
index 07305869f7..c4eca6daaa 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -459,6 +459,9 @@ PHP 8.0 UPGRADE NOTES
defines a __toString() method.
RFC: https://wiki.php.net/rfc/stringable
. Traits can now define abstract private methods.
+ RFC: https://wiki.php.net/rfc/abstract_trait_method_validation
+ . `throw` can now be used as an expression.
+ RFC: https://wiki.php.net/rfc/throw_expression
- Date:
. Added DateTime::createFromInterface() and
diff --git a/Zend/tests/throw/001.phpt b/Zend/tests/throw/001.phpt
new file mode 100644
index 0000000000..072d9f45b5
--- /dev/null
+++ b/Zend/tests/throw/001.phpt
@@ -0,0 +1,175 @@
+--TEST--
+throw expression
+--FILE--
+<?php
+
+try {
+ $result = true && throw new Exception("true && throw");
+ var_dump($result);
+} catch (Exception $e) {
+ var_dump($e->getMessage());
+}
+
+try {
+ $result = false && throw new Exception("false && throw");
+ var_dump($result);
+} catch (Exception $e) {
+ var_dump($e->getMessage());
+}
+
+try {
+ $result = true and throw new Exception("true and throw");
+ var_dump($result);
+} catch (Exception $e) {
+ var_dump($e->getMessage());
+}
+
+try {
+ $result = false and throw new Exception("false and throw");
+ var_dump($result);
+} catch (Exception $e) {
+ var_dump($e->getMessage());
+}
+
+try {
+ $result = true || throw new Exception("true || throw");
+ var_dump($result);
+} catch (Exception $e) {
+ var_dump($e->getMessage());
+}
+
+try {
+ $result = false || throw new Exception("false || throw");
+ var_dump($result);
+} catch (Exception $e) {
+ var_dump($e->getMessage());
+}
+
+try {
+ $result = true or throw new Exception("true or throw");
+ var_dump($result);
+} catch (Exception $e) {
+ var_dump($e->getMessage());
+}
+
+try {
+ $result = false or throw new Exception("false or throw");
+ var_dump($result);
+} catch (Exception $e) {
+ var_dump($e->getMessage());
+}
+
+try {
+ $result = null ?? throw new Exception("null ?? throw");
+ var_dump($result);
+} catch (Exception $e) {
+ var_dump($e->getMessage());
+}
+
+try {
+ $result = "foo" ?? throw new Exception('"foo" ?? throw');
+ var_dump($result);
+} catch (Exception $e) {
+ var_dump($e->getMessage());
+}
+
+try {
+ $result = null ?: throw new Exception("null ?: throw");
+ var_dump($result);
+} catch (Exception $e) {
+ var_dump($e->getMessage());
+}
+
+try {
+ $result = "foo" ?: throw new Exception('"foo" ?: throw');
+ var_dump($result);
+} catch (Exception $e) {
+ var_dump($e->getMessage());
+}
+
+try {
+ $callable = fn() => throw new Exception("fn() => throw");
+ var_dump("not yet");
+ $callable();
+} catch (Exception $e) {
+ var_dump($e->getMessage());
+}
+
+$result = "bar";
+try {
+ $result = throw new Exception();
+} catch (Exception $e) {}
+var_dump($result);
+
+try {
+ var_dump(
+ throw new Exception("exception 1"),
+ throw new Exception("exception 2")
+ );
+} catch (Exception $e) {
+ var_dump($e->getMessage());
+}
+
+try {
+ $result = true ? true : throw new Exception("true ? true : throw");
+ var_dump($result);
+} catch (Exception $e) {
+ var_dump($e->getMessage());
+}
+
+try {
+ $result = false ? true : throw new Exception("false ? true : throw");
+ var_dump($result);
+} catch (Exception $e) {
+ var_dump($e->getMessage());
+}
+
+try {
+ throw new Exception() + 1;
+} catch (Throwable $e) {
+ var_dump($e->getMessage());
+}
+
+try {
+ throw $exception = new Exception('throw $exception = new Exception();');
+} catch (Exception $e) {}
+var_dump($exception->getMessage());
+
+try {
+ $exception = null;
+ throw $exception ??= new Exception('throw $exception ??= new Exception();');
+} catch (Exception $e) {}
+var_dump($exception->getMessage());
+
+try {
+ throw null ?? new Exception('throw null ?? new Exception();');
+} catch (Exception $e) {
+ var_dump($e->getMessage());
+}
+
+?>
+--EXPECTF--
+string(13) "true && throw"
+bool(false)
+string(14) "true and throw"
+bool(false)
+bool(true)
+string(14) "false || throw"
+bool(true)
+string(14) "false or throw"
+string(13) "null ?? throw"
+string(3) "foo"
+string(13) "null ?: throw"
+string(3) "foo"
+string(7) "not yet"
+string(13) "fn() => throw"
+string(3) "bar"
+string(11) "exception 1"
+bool(true)
+string(20) "false ? true : throw"
+
+Notice: Object of class Exception could not be converted to number in %s on line %d
+string(22) "Can only throw objects"
+string(35) "throw $exception = new Exception();"
+string(37) "throw $exception ??= new Exception();"
+string(30) "throw null ?? new Exception();"
diff --git a/Zend/tests/throw/002.phpt b/Zend/tests/throw/002.phpt
new file mode 100644
index 0000000000..8736c27fc3
--- /dev/null
+++ b/Zend/tests/throw/002.phpt
@@ -0,0 +1,127 @@
+--TEST--
+Test throw with various expressions
+--FILE--
+<?php
+
+class Foo {
+ public function createNotFoundException() {
+ return new Exception('Not found');
+ }
+
+ public function throwException() {
+ throw $this->createNotFoundException();
+ }
+
+ public static function staticCreateNotFoundException() {
+ return new Exception('Static not found');
+ }
+
+ public static function staticThrowException() {
+ throw static::staticCreateNotFoundException();
+ }
+}
+
+try {
+ (new Foo())->throwException();
+} catch(Exception $e) {
+ echo $e->getMessage() . "\n";
+}
+
+try {
+ Foo::staticThrowException();
+} catch(Exception $e) {
+ echo $e->getMessage() . "\n";
+}
+
+try {
+ throw true ? new Exception('Ternary true 1') : new Exception('Ternary true 2');
+} catch(Exception $e) {
+ echo $e->getMessage() . "\n";
+}
+
+try {
+ throw false ? new Exception('Ternary false 1') : new Exception('Ternary false 2');
+} catch(Exception $e) {
+ echo $e->getMessage() . "\n";
+}
+
+try {
+ $exception1 = new Exception('Coalesce non-null 1');
+ $exception2 = new Exception('Coalesce non-null 2');
+ throw $exception1 ?? $exception2;
+} catch(Exception $e) {
+ echo $e->getMessage() . "\n";
+}
+
+try {
+ $exception1 = null;
+ $exception2 = new Exception('Coalesce null 2');
+ throw $exception1 ?? $exception2;
+} catch(Exception $e) {
+ echo $e->getMessage() . "\n";
+}
+
+try {
+ throw $exception = new Exception('Assignment');
+} catch(Exception $e) {
+ echo $e->getMessage() . "\n";
+}
+
+try {
+ $exception = null;
+ throw $exception ??= new Exception('Coalesce assignment null');
+} catch(Exception $e) {
+ echo $e->getMessage() . "\n";
+}
+
+try {
+ $exception = new Exception('Coalesce assignment non-null 1');
+ throw $exception ??= new Exception('Coalesce assignment non-null 2');
+} catch(Exception $e) {
+ echo $e->getMessage() . "\n";
+}
+
+$andConditionalTest = function ($condition1, $condition2) {
+ throw $condition1 && $condition2
+ ? new Exception('And in conditional 1')
+ : new Exception('And in conditional 2');
+};
+
+try {
+ $andConditionalTest(false, false);
+} catch(Exception $e) {
+ echo $e->getMessage() . "\n";
+}
+
+try {
+ $andConditionalTest(false, true);
+} catch (Exception $e) {
+ echo $e->getMessage() . "\n";
+}
+
+try {
+ $andConditionalTest(true, false);
+} catch (Exception $e) {
+ echo $e->getMessage() . "\n";
+}
+
+try {
+ $andConditionalTest(true, true);
+} catch (Exception $e) {
+ echo $e->getMessage() . "\n";
+}
+
+--EXPECT--
+Not found
+Static not found
+Ternary true 1
+Ternary false 2
+Coalesce non-null 1
+Coalesce null 2
+Assignment
+Coalesce assignment null
+Coalesce assignment non-null 1
+And in conditional 2
+And in conditional 2
+And in conditional 2
+And in conditional 1
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 2e30295980..aa108c4d7f 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -4557,7 +4557,7 @@ void zend_compile_echo(zend_ast *ast) /* {{{ */
}
/* }}} */
-void zend_compile_throw(zend_ast *ast) /* {{{ */
+void zend_compile_throw(znode *result, zend_ast *ast) /* {{{ */
{
zend_ast *expr_ast = ast->child[0];
@@ -4565,6 +4565,9 @@ void zend_compile_throw(zend_ast *ast) /* {{{ */
zend_compile_expr(&expr_node, expr_ast);
zend_emit_op(NULL, ZEND_THROW, &expr_node, NULL);
+
+ result->op_type = IS_CONST;
+ ZVAL_BOOL(&result->u.constant, 1);
}
/* }}} */
@@ -8741,9 +8744,6 @@ void zend_compile_stmt(zend_ast *ast) /* {{{ */
case ZEND_AST_ECHO:
zend_compile_echo(ast);
break;
- case ZEND_AST_THROW:
- zend_compile_throw(ast);
- break;
case ZEND_AST_BREAK:
case ZEND_AST_CONTINUE:
zend_compile_break_continue(ast);
@@ -8953,6 +8953,9 @@ void zend_compile_expr(znode *result, zend_ast *ast) /* {{{ */
case ZEND_AST_ARROW_FUNC:
zend_compile_func_decl(result, ast, 0);
return;
+ case ZEND_AST_THROW:
+ zend_compile_throw(result, ast);
+ break;
default:
ZEND_ASSERT(0 /* not supported */);
}
diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y
index c1ced9a35a..c75c1e6993 100644
--- a/Zend/zend_language_parser.y
+++ b/Zend/zend_language_parser.y
@@ -51,6 +51,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%destructor { zend_ast_destroy($$); } <ast>
%destructor { if ($$) zend_string_release_ex($$, 0); } <str>
+%precedence T_THROW
%precedence PREC_ARROW_FUNCTION
%precedence T_INCLUDE T_INCLUDE_ONCE T_REQUIRE T_REQUIRE_ONCE
%left T_LOGICAL_OR
@@ -457,7 +458,6 @@ statement:
| ';' /* empty statement */ { $$ = NULL; }
| T_TRY '{' inner_statement_list '}' catch_list finally_statement
{ $$ = zend_ast_create(ZEND_AST_TRY, $3, $5, $6); }
- | T_THROW expr ';' { $$ = zend_ast_create(ZEND_AST_THROW, $2); }
| T_GOTO T_STRING ';' { $$ = zend_ast_create(ZEND_AST_GOTO, $2); }
| T_STRING ':' { $$ = zend_ast_create(ZEND_AST_LABEL, $1); }
;
@@ -1019,6 +1019,7 @@ expr:
| T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; }
| T_YIELD expr T_DOUBLE_ARROW expr { $$ = zend_ast_create(ZEND_AST_YIELD, $4, $2); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; }
| T_YIELD_FROM expr { $$ = zend_ast_create(ZEND_AST_YIELD_FROM, $2); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; }
+ | T_THROW expr { $$ = zend_ast_create(ZEND_AST_THROW, $2); }
| inline_function { $$ = $1; }
| T_STATIC inline_function { $$ = $2; ((zend_ast_decl *) $$)->flags |= ZEND_ACC_STATIC; }
;
diff --git a/ext/tokenizer/tests/PhpToken_getAll.phpt b/ext/tokenizer/tests/PhpToken_getAll.phpt
index 604a979023..baab0a5658 100644
--- a/ext/tokenizer/tests/PhpToken_getAll.phpt
+++ b/ext/tokenizer/tests/PhpToken_getAll.phpt
@@ -32,7 +32,7 @@ array(15) {
[1]=>
object(PhpToken)#2 (4) {
["id"]=>
- int(342)
+ int(343)
["text"]=>
string(8) "function"
["line"]=>
@@ -54,7 +54,7 @@ array(15) {
[3]=>
object(PhpToken)#4 (4) {
["id"]=>
- int(310)
+ int(311)
["text"]=>
string(3) "foo"
["line"]=>
@@ -121,7 +121,7 @@ array(15) {
[9]=>
object(PhpToken)#10 (4) {
["id"]=>
- int(324)
+ int(325)
["text"]=>
string(4) "echo"
["line"]=>
@@ -143,7 +143,7 @@ array(15) {
[11]=>
object(PhpToken)#12 (4) {
["id"]=>
- int(314)
+ int(315)
["text"]=>
string(5) ""bar""
["line"]=>
@@ -202,7 +202,7 @@ array(15) {
[1]=>
object(PhpToken)#14 (4) {
["id"]=>
- int(342)
+ int(343)
["text"]=>
string(8) "function"
["line"]=>
@@ -224,7 +224,7 @@ array(15) {
[3]=>
object(PhpToken)#12 (4) {
["id"]=>
- int(310)
+ int(311)
["text"]=>
string(3) "foo"
["line"]=>
@@ -291,7 +291,7 @@ array(15) {
[9]=>
object(PhpToken)#6 (4) {
["id"]=>
- int(324)
+ int(325)
["text"]=>
string(4) "echo"
["line"]=>
@@ -313,7 +313,7 @@ array(15) {
[11]=>
object(PhpToken)#4 (4) {
["id"]=>
- int(314)
+ int(315)
["text"]=>
string(5) ""bar""
["line"]=>
diff --git a/ext/tokenizer/tests/token_get_all_variation4.phpt b/ext/tokenizer/tests/token_get_all_variation4.phpt
index 66e3cb413d..be18ddf0b3 100644
--- a/ext/tokenizer/tests/token_get_all_variation4.phpt
+++ b/ext/tokenizer/tests/token_get_all_variation4.phpt
@@ -80,7 +80,7 @@ array(88) {
[5]=>
array(3) {
[0]=>
- int(286)
+ int(%d)
[1]=>
string(2) "=="
[2]=>
diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c
index 3ddf89521a..9c7df93207 100644
--- a/ext/tokenizer/tokenizer_data.c
+++ b/ext/tokenizer/tokenizer_data.c
@@ -25,6 +25,7 @@
void tokenizer_register_constants(INIT_FUNC_ARGS) {
+ REGISTER_LONG_CONSTANT("T_THROW", T_THROW, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_INCLUDE", T_INCLUDE, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_INCLUDE_ONCE", T_INCLUDE_ONCE, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_REQUIRE", T_REQUIRE, CONST_CS | CONST_PERSISTENT);
@@ -114,7 +115,6 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) {
REGISTER_LONG_CONSTANT("T_TRY", T_TRY, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_CATCH", T_CATCH, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_FINALLY", T_FINALLY, CONST_CS | CONST_PERSISTENT);
- REGISTER_LONG_CONSTANT("T_THROW", T_THROW, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_USE", T_USE, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_INSTEADOF", T_INSTEADOF, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_GLOBAL", T_GLOBAL, CONST_CS | CONST_PERSISTENT);
@@ -168,6 +168,7 @@ char *get_token_type_name(int token_type)
{
switch (token_type) {
+ case T_THROW: return "T_THROW";
case T_INCLUDE: return "T_INCLUDE";
case T_INCLUDE_ONCE: return "T_INCLUDE_ONCE";
case T_REQUIRE: return "T_REQUIRE";
@@ -257,7 +258,6 @@ char *get_token_type_name(int token_type)
case T_TRY: return "T_TRY";
case T_CATCH: return "T_CATCH";
case T_FINALLY: return "T_FINALLY";
- case T_THROW: return "T_THROW";
case T_USE: return "T_USE";
case T_INSTEADOF: return "T_INSTEADOF";
case T_GLOBAL: return "T_GLOBAL";