Skip to content

[RFC] Make throw statement an expression #5279

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
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
175 changes: 175 additions & 0 deletions Zend/tests/throw/001.phpt
Original file line number Diff line number Diff line change
@@ -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 changes: 127 additions & 0 deletions Zend/tests/throw/002.phpt
Original file line number Diff line number Diff line change
@@ -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
11 changes: 7 additions & 4 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -4557,14 +4557,17 @@ 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];

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

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 */);
}
Expand Down
3 changes: 2 additions & 1 deletion Zend/zend_language_parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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); }
;
Expand Down Expand Up @@ -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; }
;
Expand Down
Loading