1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 08:12:21 +01:00

Make throw statement an expression

RFC: https://wiki.php.net/rfc/throw_expression

This has an open issue with temporaries that are live at the time
of the throw being leaked. Landing this now for easier testing and
will revert if we cannot resolve the issue.

Closes GH-5279.
This commit is contained in:
Ilija Tovilo
2020-03-19 00:51:51 +01:00
committed by Nikita Popov
parent 64d30c1976
commit 0810fcd0d0
8 changed files with 325 additions and 16 deletions

View File

@@ -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

175
Zend/tests/throw/001.phpt Normal file
View File

@@ -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();"

127
Zend/tests/throw/002.phpt Normal file
View File

@@ -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

View File

@@ -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 */);
}

View File

@@ -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; }
;

View File

@@ -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"]=>

View File

@@ -80,7 +80,7 @@ array(88) {
[5]=>
array(3) {
[0]=>
int(286)
int(%d)
[1]=>
string(2) "=="
[2]=>

View File

@@ -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";