Skip to content

Commit 0810fcd

Browse files
iluuu1994nikic
authored andcommittedApr 23, 2020
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 phpGH-5279.
1 parent 64d30c1 commit 0810fcd

File tree

8 files changed

+325
-16
lines changed

8 files changed

+325
-16
lines changed
 

‎UPGRADING

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,9 @@ PHP 8.0 UPGRADE NOTES
459459
defines a __toString() method.
460460
RFC: https://wiki.php.net/rfc/stringable
461461
. Traits can now define abstract private methods.
462+
RFC: https://wiki.php.net/rfc/abstract_trait_method_validation
463+
. `throw` can now be used as an expression.
464+
RFC: https://wiki.php.net/rfc/throw_expression
462465

463466
- Date:
464467
. Added DateTime::createFromInterface() and

‎Zend/tests/throw/001.phpt

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
--TEST--
2+
throw expression
3+
--FILE--
4+
<?php
5+
6+
try {
7+
$result = true && throw new Exception("true && throw");
8+
var_dump($result);
9+
} catch (Exception $e) {
10+
var_dump($e->getMessage());
11+
}
12+
13+
try {
14+
$result = false && throw new Exception("false && throw");
15+
var_dump($result);
16+
} catch (Exception $e) {
17+
var_dump($e->getMessage());
18+
}
19+
20+
try {
21+
$result = true and throw new Exception("true and throw");
22+
var_dump($result);
23+
} catch (Exception $e) {
24+
var_dump($e->getMessage());
25+
}
26+
27+
try {
28+
$result = false and throw new Exception("false and throw");
29+
var_dump($result);
30+
} catch (Exception $e) {
31+
var_dump($e->getMessage());
32+
}
33+
34+
try {
35+
$result = true || throw new Exception("true || throw");
36+
var_dump($result);
37+
} catch (Exception $e) {
38+
var_dump($e->getMessage());
39+
}
40+
41+
try {
42+
$result = false || throw new Exception("false || throw");
43+
var_dump($result);
44+
} catch (Exception $e) {
45+
var_dump($e->getMessage());
46+
}
47+
48+
try {
49+
$result = true or throw new Exception("true or throw");
50+
var_dump($result);
51+
} catch (Exception $e) {
52+
var_dump($e->getMessage());
53+
}
54+
55+
try {
56+
$result = false or throw new Exception("false or throw");
57+
var_dump($result);
58+
} catch (Exception $e) {
59+
var_dump($e->getMessage());
60+
}
61+
62+
try {
63+
$result = null ?? throw new Exception("null ?? throw");
64+
var_dump($result);
65+
} catch (Exception $e) {
66+
var_dump($e->getMessage());
67+
}
68+
69+
try {
70+
$result = "foo" ?? throw new Exception('"foo" ?? throw');
71+
var_dump($result);
72+
} catch (Exception $e) {
73+
var_dump($e->getMessage());
74+
}
75+
76+
try {
77+
$result = null ?: throw new Exception("null ?: throw");
78+
var_dump($result);
79+
} catch (Exception $e) {
80+
var_dump($e->getMessage());
81+
}
82+
83+
try {
84+
$result = "foo" ?: throw new Exception('"foo" ?: throw');
85+
var_dump($result);
86+
} catch (Exception $e) {
87+
var_dump($e->getMessage());
88+
}
89+
90+
try {
91+
$callable = fn() => throw new Exception("fn() => throw");
92+
var_dump("not yet");
93+
$callable();
94+
} catch (Exception $e) {
95+
var_dump($e->getMessage());
96+
}
97+
98+
$result = "bar";
99+
try {
100+
$result = throw new Exception();
101+
} catch (Exception $e) {}
102+
var_dump($result);
103+
104+
try {
105+
var_dump(
106+
throw new Exception("exception 1"),
107+
throw new Exception("exception 2")
108+
);
109+
} catch (Exception $e) {
110+
var_dump($e->getMessage());
111+
}
112+
113+
try {
114+
$result = true ? true : throw new Exception("true ? true : throw");
115+
var_dump($result);
116+
} catch (Exception $e) {
117+
var_dump($e->getMessage());
118+
}
119+
120+
try {
121+
$result = false ? true : throw new Exception("false ? true : throw");
122+
var_dump($result);
123+
} catch (Exception $e) {
124+
var_dump($e->getMessage());
125+
}
126+
127+
try {
128+
throw new Exception() + 1;
129+
} catch (Throwable $e) {
130+
var_dump($e->getMessage());
131+
}
132+
133+
try {
134+
throw $exception = new Exception('throw $exception = new Exception();');
135+
} catch (Exception $e) {}
136+
var_dump($exception->getMessage());
137+
138+
try {
139+
$exception = null;
140+
throw $exception ??= new Exception('throw $exception ??= new Exception();');
141+
} catch (Exception $e) {}
142+
var_dump($exception->getMessage());
143+
144+
try {
145+
throw null ?? new Exception('throw null ?? new Exception();');
146+
} catch (Exception $e) {
147+
var_dump($e->getMessage());
148+
}
149+
150+
?>
151+
--EXPECTF--
152+
string(13) "true && throw"
153+
bool(false)
154+
string(14) "true and throw"
155+
bool(false)
156+
bool(true)
157+
string(14) "false || throw"
158+
bool(true)
159+
string(14) "false or throw"
160+
string(13) "null ?? throw"
161+
string(3) "foo"
162+
string(13) "null ?: throw"
163+
string(3) "foo"
164+
string(7) "not yet"
165+
string(13) "fn() => throw"
166+
string(3) "bar"
167+
string(11) "exception 1"
168+
bool(true)
169+
string(20) "false ? true : throw"
170+
171+
Notice: Object of class Exception could not be converted to number in %s on line %d
172+
string(22) "Can only throw objects"
173+
string(35) "throw $exception = new Exception();"
174+
string(37) "throw $exception ??= new Exception();"
175+
string(30) "throw null ?? new Exception();"

‎Zend/tests/throw/002.phpt

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
--TEST--
2+
Test throw with various expressions
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public function createNotFoundException() {
8+
return new Exception('Not found');
9+
}
10+
11+
public function throwException() {
12+
throw $this->createNotFoundException();
13+
}
14+
15+
public static function staticCreateNotFoundException() {
16+
return new Exception('Static not found');
17+
}
18+
19+
public static function staticThrowException() {
20+
throw static::staticCreateNotFoundException();
21+
}
22+
}
23+
24+
try {
25+
(new Foo())->throwException();
26+
} catch(Exception $e) {
27+
echo $e->getMessage() . "\n";
28+
}
29+
30+
try {
31+
Foo::staticThrowException();
32+
} catch(Exception $e) {
33+
echo $e->getMessage() . "\n";
34+
}
35+
36+
try {
37+
throw true ? new Exception('Ternary true 1') : new Exception('Ternary true 2');
38+
} catch(Exception $e) {
39+
echo $e->getMessage() . "\n";
40+
}
41+
42+
try {
43+
throw false ? new Exception('Ternary false 1') : new Exception('Ternary false 2');
44+
} catch(Exception $e) {
45+
echo $e->getMessage() . "\n";
46+
}
47+
48+
try {
49+
$exception1 = new Exception('Coalesce non-null 1');
50+
$exception2 = new Exception('Coalesce non-null 2');
51+
throw $exception1 ?? $exception2;
52+
} catch(Exception $e) {
53+
echo $e->getMessage() . "\n";
54+
}
55+
56+
try {
57+
$exception1 = null;
58+
$exception2 = new Exception('Coalesce null 2');
59+
throw $exception1 ?? $exception2;
60+
} catch(Exception $e) {
61+
echo $e->getMessage() . "\n";
62+
}
63+
64+
try {
65+
throw $exception = new Exception('Assignment');
66+
} catch(Exception $e) {
67+
echo $e->getMessage() . "\n";
68+
}
69+
70+
try {
71+
$exception = null;
72+
throw $exception ??= new Exception('Coalesce assignment null');
73+
} catch(Exception $e) {
74+
echo $e->getMessage() . "\n";
75+
}
76+
77+
try {
78+
$exception = new Exception('Coalesce assignment non-null 1');
79+
throw $exception ??= new Exception('Coalesce assignment non-null 2');
80+
} catch(Exception $e) {
81+
echo $e->getMessage() . "\n";
82+
}
83+
84+
$andConditionalTest = function ($condition1, $condition2) {
85+
throw $condition1 && $condition2
86+
? new Exception('And in conditional 1')
87+
: new Exception('And in conditional 2');
88+
};
89+
90+
try {
91+
$andConditionalTest(false, false);
92+
} catch(Exception $e) {
93+
echo $e->getMessage() . "\n";
94+
}
95+
96+
try {
97+
$andConditionalTest(false, true);
98+
} catch (Exception $e) {
99+
echo $e->getMessage() . "\n";
100+
}
101+
102+
try {
103+
$andConditionalTest(true, false);
104+
} catch (Exception $e) {
105+
echo $e->getMessage() . "\n";
106+
}
107+
108+
try {
109+
$andConditionalTest(true, true);
110+
} catch (Exception $e) {
111+
echo $e->getMessage() . "\n";
112+
}
113+
114+
--EXPECT--
115+
Not found
116+
Static not found
117+
Ternary true 1
118+
Ternary false 2
119+
Coalesce non-null 1
120+
Coalesce null 2
121+
Assignment
122+
Coalesce assignment null
123+
Coalesce assignment non-null 1
124+
And in conditional 2
125+
And in conditional 2
126+
And in conditional 2
127+
And in conditional 1

‎Zend/zend_compile.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4557,14 +4557,17 @@ void zend_compile_echo(zend_ast *ast) /* {{{ */
45574557
}
45584558
/* }}} */
45594559

4560-
void zend_compile_throw(zend_ast *ast) /* {{{ */
4560+
void zend_compile_throw(znode *result, zend_ast *ast) /* {{{ */
45614561
{
45624562
zend_ast *expr_ast = ast->child[0];
45634563

45644564
znode expr_node;
45654565
zend_compile_expr(&expr_node, expr_ast);
45664566

45674567
zend_emit_op(NULL, ZEND_THROW, &expr_node, NULL);
4568+
4569+
result->op_type = IS_CONST;
4570+
ZVAL_BOOL(&result->u.constant, 1);
45684571
}
45694572
/* }}} */
45704573

@@ -8741,9 +8744,6 @@ void zend_compile_stmt(zend_ast *ast) /* {{{ */
87418744
case ZEND_AST_ECHO:
87428745
zend_compile_echo(ast);
87438746
break;
8744-
case ZEND_AST_THROW:
8745-
zend_compile_throw(ast);
8746-
break;
87478747
case ZEND_AST_BREAK:
87488748
case ZEND_AST_CONTINUE:
87498749
zend_compile_break_continue(ast);
@@ -8953,6 +8953,9 @@ void zend_compile_expr(znode *result, zend_ast *ast) /* {{{ */
89538953
case ZEND_AST_ARROW_FUNC:
89548954
zend_compile_func_decl(result, ast, 0);
89558955
return;
8956+
case ZEND_AST_THROW:
8957+
zend_compile_throw(result, ast);
8958+
break;
89568959
default:
89578960
ZEND_ASSERT(0 /* not supported */);
89588961
}

‎Zend/zend_language_parser.y

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
5151
%destructor { zend_ast_destroy($$); } <ast>
5252
%destructor { if ($$) zend_string_release_ex($$, 0); } <str>
5353

54+
%precedence T_THROW
5455
%precedence PREC_ARROW_FUNCTION
5556
%precedence T_INCLUDE T_INCLUDE_ONCE T_REQUIRE T_REQUIRE_ONCE
5657
%left T_LOGICAL_OR
@@ -457,7 +458,6 @@ statement:
457458
| ';' /* empty statement */ { $$ = NULL; }
458459
| T_TRY '{' inner_statement_list '}' catch_list finally_statement
459460
{ $$ = zend_ast_create(ZEND_AST_TRY, $3, $5, $6); }
460-
| T_THROW expr ';' { $$ = zend_ast_create(ZEND_AST_THROW, $2); }
461461
| T_GOTO T_STRING ';' { $$ = zend_ast_create(ZEND_AST_GOTO, $2); }
462462
| T_STRING ':' { $$ = zend_ast_create(ZEND_AST_LABEL, $1); }
463463
;
@@ -1019,6 +1019,7 @@ expr:
10191019
| T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; }
10201020
| T_YIELD expr T_DOUBLE_ARROW expr { $$ = zend_ast_create(ZEND_AST_YIELD, $4, $2); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; }
10211021
| T_YIELD_FROM expr { $$ = zend_ast_create(ZEND_AST_YIELD_FROM, $2); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; }
1022+
| T_THROW expr { $$ = zend_ast_create(ZEND_AST_THROW, $2); }
10221023
| inline_function { $$ = $1; }
10231024
| T_STATIC inline_function { $$ = $2; ((zend_ast_decl *) $$)->flags |= ZEND_ACC_STATIC; }
10241025
;

0 commit comments

Comments
 (0)
Please sign in to comment.