From 906e50cedaa43efd5620f08624a2a14431d367f5 Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Mon, 6 Sep 2021 23:06:59 +0300 Subject: [PATCH 1/9] added initial match support (without default) --- compiler/data/vertex-adaptor.h | 2 +- compiler/debug.cpp | 1 + compiler/gentree.cpp | 83 +++++++++++++++++++++++++ compiler/gentree.h | 5 ++ compiler/keywords.gperf | 1 + compiler/pipes/calc-rl.cpp | 7 ++- compiler/pipes/gen-tree-postprocess.cpp | 71 +++++++++++++++++++-- compiler/token.h | 1 + compiler/vertex-desc.json | 42 +++++++++++++ 9 files changed, 206 insertions(+), 7 deletions(-) diff --git a/compiler/data/vertex-adaptor.h b/compiler/data/vertex-adaptor.h index 95f037ad8d..b474ec36e5 100644 --- a/compiler/data/vertex-adaptor.h +++ b/compiler/data/vertex-adaptor.h @@ -55,7 +55,7 @@ class VertexAdaptor { return *this; } - VertexAdaptor &operator=(const VertexAdaptor &) & = default; +// VertexAdaptor &operator=(const VertexAdaptor &) & = default; explicit operator bool() const { return impl != nullptr; diff --git a/compiler/debug.cpp b/compiler/debug.cpp index 0164a5f4d3..8d891f1458 100644 --- a/compiler/debug.cpp +++ b/compiler/debug.cpp @@ -57,6 +57,7 @@ std::string debugTokenName(TokenType t) { {tok_as, "tok_as"}, {tok_case, "tok_case"}, {tok_switch, "tok_switch"}, + {tok_match, "tok_match"}, {tok_class, "tok_class"}, {tok_interface, "tok_interface"}, {tok_trait, "tok_trait"}, diff --git a/compiler/gentree.cpp b/compiler/gentree.cpp index aebdcd0ddc..dc1f9c2a53 100644 --- a/compiler/gentree.cpp +++ b/compiler/gentree.cpp @@ -660,6 +660,11 @@ VertexPtr GenTree::get_expr_top(bool was_arrow) { case tok_shape: res = get_shape(); break; + + case tok_match: + res = get_match(); + break; + case tok_opbrk: res = get_short_array(); return_flag = false; // array is valid postfix expression operand @@ -1315,6 +1320,84 @@ VertexAdaptor GenTree::get_switch() { return create_switch_vertex(cur_function, switch_condition, std::move(cases)).set_location(location); } +VertexAdaptor GenTree::create_match_vertex(FunctionPtr cur_function, VertexPtr match_condition, std::vector &&cases) { + auto temp_var_condition_on_switch = create_superlocal_var("condition_on_match", cur_function); + auto temp_var_matched_with_one_case = create_superlocal_var("matched_with_one_case", cur_function); + return VertexAdaptor::create(match_condition, temp_var_condition_on_switch, temp_var_matched_with_one_case, std::move(cases)); +} + +VertexAdaptor GenTree::get_match_case() { + auto location = auto_location(); + + std::vector conditions; + + while (cur->type() != tok_double_arrow) { + const auto cur_type = cur->type(); + if (cur_type == tok_comma) { + next_cur(); + continue; + } + + const auto condition = get_expression(); + + if (const auto double_arrow = condition.try_as()) { + // short $expr => $return_expr + if (conditions.empty()) { + const auto return_expr = double_arrow->value(); + const auto conditions_vertex = VertexAdaptor::create(std::vector{double_arrow->key()}); + + return VertexAdaptor::create(conditions_vertex, return_expr).set_location(location); + } + + // add last condition + conditions.push_back(double_arrow->key()); + + const auto return_expr = double_arrow->value(); + const auto conditions_vertex = VertexAdaptor::create(conditions); + + return VertexAdaptor::create(conditions_vertex, return_expr).set_location(location); + } + + conditions.push_back(condition); + } + CE (expect(tok_double_arrow, "'=>'")); + + const auto return_expr = get_expression(); + if (cur->type() == tok_comma) { + next_cur(); + } + + return VertexAdaptor::create(VertexAdaptor::create(conditions), return_expr).set_location(location); +} + +VertexAdaptor GenTree::get_match() { + auto location = auto_location(); + + next_cur(); + CE (expect(tok_oppar, "'('")); + skip_phpdoc_tokens(); + auto match_condition = get_expression(); + CE (!kphp_error(match_condition, "Failed to parse 'match' expression")); + CE (expect(tok_clpar, "')'")); + + CE (expect(tok_opbrc, "'{'")); + + vector cases; + while (cur->type() != tok_clbrc) { + skip_phpdoc_tokens(); + auto cur_type = cur->type(); + if (cur_type == tok_comma) { + next_cur(); + continue; + } + + cases.emplace_back(get_match_case()); + } + CE (expect(tok_clbrc, "'}'")); + + return create_match_vertex(cur_function, match_condition, std::move(cases)).set_location(location); +} + VertexAdaptor GenTree::get_shape() { auto location = auto_location(); diff --git a/compiler/gentree.h b/compiler/gentree.h index 31fe78c097..1b9b8f7e3e 100644 --- a/compiler/gentree.h +++ b/compiler/gentree.h @@ -41,6 +41,7 @@ class GenTree { VertexAdaptor create_superlocal_var(const std::string& name_prefix); static VertexAdaptor create_superlocal_var(const std::string& name_prefix, FunctionPtr cur_function); static VertexAdaptor create_switch_vertex(FunctionPtr cur_function, VertexPtr switch_condition, std::vector &&cases); + static VertexAdaptor create_match_vertex(FunctionPtr cur_function, VertexPtr match_condition, std::vector &&cases); static bool is_superglobal(const string &s); static bool is_magic_method_name_allowed(const std::string &name); @@ -120,7 +121,11 @@ class GenTree { VertexAdaptor get_if(); VertexAdaptor get_for(); VertexAdaptor get_do(); + VertexAdaptor get_switch(); + VertexAdaptor get_match_case(); + VertexAdaptor get_match(); + VertexAdaptor get_shape(); VertexPtr get_phpdoc_inside_function(); bool parse_function_uses(std::vector> *uses_of_lambda); diff --git a/compiler/keywords.gperf b/compiler/keywords.gperf index 2e48d8470a..5fcc1040a5 100644 --- a/compiler/keywords.gperf +++ b/compiler/keywords.gperf @@ -28,6 +28,7 @@ Array, tok_array as, tok_as case, tok_case switch, tok_switch +match, tok_match class, tok_class interface, tok_interface trait, tok_trait diff --git a/compiler/pipes/calc-rl.cpp b/compiler/pipes/calc-rl.cpp index 07e1d403b5..69fd0b1da3 100644 --- a/compiler/pipes/calc-rl.cpp +++ b/compiler/pipes/calc-rl.cpp @@ -84,7 +84,12 @@ void rl_other_calc(VertexPtr root, RLValueType expected_rl_type) { case op_list_keyval: { const auto kv = root.as(); rl_calc(kv->key(), val_r); - rl_calc(kv->var(), val_l); + + if (auto array = kv->var().try_as()) { + rl_calc_all(array, array->size() - 1); + } else { + rl_calc(kv->var(), val_l); + } break; } default: diff --git a/compiler/pipes/gen-tree-postprocess.cpp b/compiler/pipes/gen-tree-postprocess.cpp index 0b1cf0c506..ee5893e674 100644 --- a/compiler/pipes/gen-tree-postprocess.cpp +++ b/compiler/pipes/gen-tree-postprocess.cpp @@ -9,6 +9,7 @@ #include "compiler/data/lib-data.h" #include "compiler/data/src-file.h" #include "compiler/gentree.h" +#include "compiler/name-gen.h" namespace { template @@ -40,13 +41,10 @@ VertexAdaptor convert_set_null_coalesce(VertexAdaptor::create(v->lhs().clone(), rhs).set_location(v); } -VertexAdaptor make_list_op(VertexAdaptor assign) { +vector> convert_list_part(VertexPtr list, VertexPtr arr) { bool has_explicit_keys = false; bool has_empty_entries = false; // To give the same error message as PHP - auto list = assign->lhs(); - auto arr = assign->rhs(); - std::vector> mappings; mappings.reserve(list->size()); @@ -55,7 +53,15 @@ VertexAdaptor make_list_op(VertexAdaptor assign) { for (auto x : *list) { if (const auto arrow = x.try_as()) { has_explicit_keys = true; - mappings.emplace_back(VertexAdaptor::create(arrow->lhs(), arrow->rhs())); + + if (auto array = arrow->rhs().try_as()) { + const auto rhs_mappings = convert_list_part(array, arr); + const auto array_vertex = VertexAdaptor::create(mappings, arr); + mappings.emplace_back(VertexAdaptor::create(arrow->lhs(), array_vertex)); + } else { + mappings.emplace_back(VertexAdaptor::create(arrow->lhs(), arrow->rhs())); + } + continue; } @@ -77,6 +83,14 @@ VertexAdaptor make_list_op(VertexAdaptor assign) { } else if (has_explicit_keys && has_implicit_keys) { kphp_error(0, "Cannot mix keyed and unkeyed array entries in assignments"); } + return mappings; +} + +VertexAdaptor make_list_op(VertexAdaptor assign) { + auto list = assign->lhs(); + auto arr = assign->rhs(); + + const auto mappings = convert_list_part(list, arr); return VertexAdaptor::create(mappings, arr).set_location(assign); } @@ -232,6 +246,43 @@ VertexPtr GenTreePostprocessPass::on_enter_vertex(VertexPtr root) { return root; } +VertexAdaptor create_switch_vertex_from_match(VertexAdaptor match, VertexAdaptor local_var) { + auto switch_condition = match->condition(); + auto temp_var_condition_on_switch = match->condition_on_match(); + auto temp_var_matched_with_one_case = match->matched_with_one_case(); + auto cases = match->cases(); + + std::vector switch_cases; + switch_cases.reserve(cases.size()); + + for (const auto case_vertex : cases) { + if (const auto match_case = case_vertex.try_as()) { + + const auto set_vertex = VertexAdaptor::create(local_var, match_case->return_expr()); + const auto break_vertex = VertexAdaptor::create(GenTree::create_int_const(1)); + const auto stmts = std::vector{set_vertex, break_vertex}; + const auto seq = VertexAdaptor::create(stmts).set_location(match_case); + const auto empty_seq = VertexAdaptor::create().set_location(match_case); + + const auto conditions = match_case->conditions()->args(); + const auto condition_count = conditions.size(); + + auto current_condition = 0; + for (const auto condition : match_case->conditions()->args()) { + if (current_condition == condition_count - 1) { + switch_cases.emplace_back(VertexAdaptor::create(condition, seq)); + continue; + } + + switch_cases.emplace_back(VertexAdaptor::create(condition, empty_seq)); + ++current_condition; + } + } + } + + return VertexAdaptor::create(switch_condition, temp_var_condition_on_switch, temp_var_matched_with_one_case, std::move(switch_cases)); +} + VertexPtr GenTreePostprocessPass::on_exit_vertex(VertexPtr root) { if (root->type() == op_var) { if (GenTree::is_superglobal(root->get_string())) { @@ -256,6 +307,16 @@ VertexPtr GenTreePostprocessPass::on_exit_vertex(VertexPtr root) { return convert_array_with_spread_operators(array); } + if (auto match = root.try_as()) { + auto tmp_var = VertexAdaptor::create(); + tmp_var->set_string(gen_unique_name("tmp_var")); + tmp_var->extra_type = op_ex_var_superlocal; + + const auto switch_vertex = create_switch_vertex_from_match(match, tmp_var); + const auto result_seq = VertexAdaptor::create(switch_vertex, tmp_var); + return result_seq; + } + return root; } diff --git a/compiler/token.h b/compiler/token.h index fecd6473d6..00bcab619d 100644 --- a/compiler/token.h +++ b/compiler/token.h @@ -43,6 +43,7 @@ enum TokenType { tok_as, tok_case, tok_switch, + tok_match, tok_class, tok_interface, tok_trait, diff --git a/compiler/vertex-desc.json b/compiler/vertex-desc.json index 1cfba930a4..b905cb3aa5 100644 --- a/compiler/vertex-desc.json +++ b/compiler/vertex-desc.json @@ -588,6 +588,48 @@ ] } }, + { + "comment": "match (condition()) { cases()... }", + "sons": { + "condition": 0, + "condition_on_match" : { + "id": 1, + "type": "op_var" + }, + "matched_with_one_case" : { + "id": 2, + "type": "op_var" + } + }, + "name": "op_match", + "base_name": "meta_op_base", + "props": { + "str": "match" + }, + "ranges": { + "cases": [ + 3, + 0 + ] + } + }, + { + "comment": "$expr1, $expr2, ... => $return_expr", + "sons": { + "conditions" : { + "id": 0, + "type": "op_seq_comma" + }, + "return_expr" : { + "id": 1 + } + }, + "name": "op_match_case", + "base_name": "meta_op_base", + "props": { + "str": "case" + } + }, { "comment": "if (cond()) true_cmd(); or if (cond()) true_cmd() else false_cmd()", "sons": { From 4fbc572e4e61a24c032cba46f167927d8abf33c6 Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Tue, 7 Sep 2021 00:50:35 +0300 Subject: [PATCH 2/9] added support for default --- compiler/gentree.cpp | 39 ++++++-- compiler/gentree.h | 9 +- compiler/pipes/gen-tree-postprocess.cpp | 124 ++++++++++++++++-------- compiler/pipes/gen-tree-postprocess.h | 65 +++++++++++++ compiler/vertex-desc.json | 13 +++ 5 files changed, 202 insertions(+), 48 deletions(-) diff --git a/compiler/gentree.cpp b/compiler/gentree.cpp index dc1f9c2a53..cb89dbeed0 100644 --- a/compiler/gentree.cpp +++ b/compiler/gentree.cpp @@ -1326,13 +1326,37 @@ VertexAdaptor GenTree::create_match_vertex(FunctionPtr cur_function, V return VertexAdaptor::create(match_condition, temp_var_condition_on_switch, temp_var_matched_with_one_case, std::move(cases)); } -VertexAdaptor GenTree::get_match_case() { +VertexAdaptor GenTree::get_match_default() { + auto location = auto_location(); + next_cur(); + // 'default, => $return' + // | comma allowed + if (cur->type() == tok_comma) { + next_cur(); + } + CE (expect(tok_double_arrow, "'=>'")); + + const auto return_expr = get_expression(); + if (cur->type() == tok_comma) { + next_cur(); + } + + return VertexAdaptor::create(return_expr).set_location(location); +} + +VertexPtr GenTree::get_match_case() { auto location = auto_location(); std::vector conditions; + if (cur->type() == tok_default) { + return get_match_default(); + } + while (cur->type() != tok_double_arrow) { const auto cur_type = cur->type(); + // '$expr, => $return' + // | comma allowed if (cur_type == tok_comma) { next_cur(); continue; @@ -1341,7 +1365,7 @@ VertexAdaptor GenTree::get_match_case() { const auto condition = get_expression(); if (const auto double_arrow = condition.try_as()) { - // short $expr => $return_expr + // if simple $expr => $return_expr if (conditions.empty()) { const auto return_expr = double_arrow->value(); const auto conditions_vertex = VertexAdaptor::create(std::vector{double_arrow->key()}); @@ -1349,6 +1373,7 @@ VertexAdaptor GenTree::get_match_case() { return VertexAdaptor::create(conditions_vertex, return_expr).set_location(location); } + // if several conditions // add last condition conditions.push_back(double_arrow->key()); @@ -1362,12 +1387,10 @@ VertexAdaptor GenTree::get_match_case() { } CE (expect(tok_double_arrow, "'=>'")); - const auto return_expr = get_expression(); - if (cur->type() == tok_comma) { - next_cur(); - } - - return VertexAdaptor::create(VertexAdaptor::create(conditions), return_expr).set_location(location); + // Reachable only if arm does not contain '$expr => $return_expr'. + // Note that arm '$expr1, $expr2 => $return_expr' contains '$expr => $return_expr'. + kphp_error(0, "Bad match arm"); + return VertexPtr{}; } VertexAdaptor GenTree::get_match() { diff --git a/compiler/gentree.h b/compiler/gentree.h index 1b9b8f7e3e..21fdaef5ec 100644 --- a/compiler/gentree.h +++ b/compiler/gentree.h @@ -75,6 +75,12 @@ class GenTree { return int_v; } + static auto create_string_const(const std::string &str) { + auto string_vertex = VertexAdaptor::create(); + string_vertex->str_val = str; + return string_vertex; + } + VertexAdaptor get_func_param(); VertexAdaptor get_var_name(); VertexAdaptor get_var_name_ref(); @@ -123,7 +129,8 @@ class GenTree { VertexAdaptor get_do(); VertexAdaptor get_switch(); - VertexAdaptor get_match_case(); + VertexAdaptor get_match_default(); + VertexPtr get_match_case(); VertexAdaptor get_match(); VertexAdaptor get_shape(); diff --git a/compiler/pipes/gen-tree-postprocess.cpp b/compiler/pipes/gen-tree-postprocess.cpp index ee5893e674..4a4a635497 100644 --- a/compiler/pipes/gen-tree-postprocess.cpp +++ b/compiler/pipes/gen-tree-postprocess.cpp @@ -246,18 +246,57 @@ VertexPtr GenTreePostprocessPass::on_enter_vertex(VertexPtr root) { return root; } +VertexPtr GenTreePostprocessPass::on_exit_vertex(VertexPtr root) { + if (root->type() == op_var) { + if (GenTree::is_superglobal(root->get_string())) { + root->extra_type = op_ex_var_superglobal; + } + } + if (auto fork_call = root.try_as()) { + if (fork_call->size() != 1 || fork_call->back()->type() != op_func_call) { + kphp_error(0, "Fork argument must be function call"); + return root; + } + } + + if (auto call = root.try_as()) { + if (!G->settings().profiler_level.get() && call->size() == 1 && + vk::any_of_equal(call->get_string(), "profiler_set_log_suffix", "profiler_set_function_label")) { + return VertexAdaptor::create(VertexAdaptor::create(), GenTree::embrace(call)).set_location_recursively(root); + } + } + + if (auto array = root.try_as()) { + return convert_array_with_spread_operators(array); + } + + if (auto match = root.try_as()) { + return convert_match_to_switch(match); + } + + return root; +} + VertexAdaptor create_switch_vertex_from_match(VertexAdaptor match, VertexAdaptor local_var) { - auto switch_condition = match->condition(); - auto temp_var_condition_on_switch = match->condition_on_match(); - auto temp_var_matched_with_one_case = match->matched_with_one_case(); - auto cases = match->cases(); + const auto switch_condition = match->condition(); + const auto temp_var_condition_on_switch = match->condition_on_match(); + const auto temp_var_matched_with_one_case = match->matched_with_one_case(); + const auto cases = match->cases(); std::vector switch_cases; switch_cases.reserve(cases.size()); + VertexAdaptor default_case; + for (const auto case_vertex : cases) { + if (const auto default_case_vertex = case_vertex.try_as()) { + if (default_case) { + kphp_error(0, "Match expressions may only contain one default arm"); + } + default_case = default_case_vertex; + continue; + } if (const auto match_case = case_vertex.try_as()) { - const auto set_vertex = VertexAdaptor::create(local_var, match_case->return_expr()); const auto break_vertex = VertexAdaptor::create(GenTree::create_int_const(1)); const auto stmts = std::vector{set_vertex, break_vertex}; @@ -267,6 +306,14 @@ VertexAdaptor create_switch_vertex_from_match(VertexAdaptor const auto conditions = match_case->conditions()->args(); const auto condition_count = conditions.size(); + // We create a case body only for the last case for several conditions, so as not to duplicate the code. + // For example: + // $a = match($b) { 10, 20 => 1 }; + // Converts to something like this switch: + // switch ($b) { + // case 10: {} // fallthrough + // case 20: { $a = 1; break; } + // } auto current_condition = 0; for (const auto condition : match_case->conditions()->args()) { if (current_condition == condition_count - 1) { @@ -280,44 +327,43 @@ VertexAdaptor create_switch_vertex_from_match(VertexAdaptor } } - return VertexAdaptor::create(switch_condition, temp_var_condition_on_switch, temp_var_matched_with_one_case, std::move(switch_cases)); -} - -VertexPtr GenTreePostprocessPass::on_exit_vertex(VertexPtr root) { - if (root->type() == op_var) { - if (GenTree::is_superglobal(root->get_string())) { - root->extra_type = op_ex_var_superglobal; - } - } - if (auto fork_call = root.try_as()) { - if (fork_call->size() != 1 || fork_call->back()->type() != op_func_call) { - kphp_error(0, "Fork argument must be function call"); - return root; - } - } - - if (auto call = root.try_as()) { - if (!G->settings().profiler_level.get() && call->size() == 1 && - vk::any_of_equal(call->get_string(), "profiler_set_log_suffix", "profiler_set_function_label")) { - return VertexAdaptor::create(VertexAdaptor::create(), GenTree::embrace(call)).set_location_recursively(root); - } - } - - if (auto array = root.try_as()) { - return convert_array_with_spread_operators(array); + if (default_case) { + const auto set_vertex = VertexAdaptor::create(local_var, default_case->return_expr()); + const auto break_vertex = VertexAdaptor::create(GenTree::create_int_const(1)); + const auto stmts = std::vector{set_vertex, break_vertex}; + const auto seq = VertexAdaptor::create(stmts).set_location(default_case); + + switch_cases.emplace_back(VertexAdaptor::create(seq)); + } else { + // If there is no default case, then create a default one, + // which will give warning to any unhandled value. + // + // PHP throws an UnhandledMatchError if the value is unhandled, + // but we only give warning. + const auto message = VertexAdaptor::create(std::vector{ + GenTree::create_string_const("Unhandled match value '"), + match->condition().clone(), + GenTree::create_string_const("'") + }); + const auto args = std::vector{message}; + auto call_function = VertexAdaptor::create(args); + call_function->set_string("warning"); + + const auto seq = VertexAdaptor::create(std::vector{call_function}); + + switch_cases.emplace_back(VertexAdaptor::create(seq)); } - if (auto match = root.try_as()) { - auto tmp_var = VertexAdaptor::create(); - tmp_var->set_string(gen_unique_name("tmp_var")); - tmp_var->extra_type = op_ex_var_superlocal; + return VertexAdaptor::create(switch_condition, temp_var_condition_on_switch, temp_var_matched_with_one_case, std::move(switch_cases)); +} - const auto switch_vertex = create_switch_vertex_from_match(match, tmp_var); - const auto result_seq = VertexAdaptor::create(switch_vertex, tmp_var); - return result_seq; - } +VertexPtr GenTreePostprocessPass::convert_match_to_switch(VertexAdaptor &match) { + auto tmp_var = VertexAdaptor::create(); + tmp_var->set_string(gen_unique_name("tmp_var")); + tmp_var->extra_type = op_ex_var_superlocal; - return root; + const auto switch_vertex = create_switch_vertex_from_match(match, tmp_var); + return VertexAdaptor::create(switch_vertex, tmp_var); } VertexAdaptor array_vertex_from_slice(const VertexRange &args, size_t start, size_t end) { diff --git a/compiler/pipes/gen-tree-postprocess.h b/compiler/pipes/gen-tree-postprocess.h index 82f262878c..ce3c859e7e 100644 --- a/compiler/pipes/gen-tree-postprocess.h +++ b/compiler/pipes/gen-tree-postprocess.h @@ -23,4 +23,69 @@ class GenTreePostprocessPass final : public FunctionPassBase { // converts the spread operator (...$a) to a call to the array_merge_spread function static VertexPtr convert_array_with_spread_operators(VertexAdaptor array_vertex); + + // converts the match($a) { ... } to a rvalue statements list + // For example: + // $a = match($b) { + // 10 => 1, + // 20 => 2, + // default => 3, + // } + // Converts to something like this: + // $a = ({ + // switch ($b) { + // case 10: { + // $tmp = 1; break; + // } + // case 20: { + // $tmp = 2; break; + // } + // default: { + // $tmp = 3; break; + // } + // }; + // $tmp; + // }); + // + // If there are several conditions, then the code is generated only once + // and all conditions fallthrough to this code: + // + // For example: + // $a = match($b) { + // 10, 20 => 1, + // default => 3, + // } + // Converts to something like this: + // $a = ({ + // switch ($b) { + // case 10: {} // fallthrough + // case 20: { + // $tmp = 1; break; + // } + // default: { + // $tmp = 3; break; + // } + // }; + // $tmp; + // }); + // + // If there is no default branch, then the default version is generated. + // The default version always gives a warning about an unhandled value. + // For example: + // $a = match($b) { + // 10 => 1, + // } + // Converts to something like this: + // $a = ({ + // switch ($b) { + // case 10: { + // $tmp = 1; break; + // } + // default: { + // warning("Unhandled match value '$b'"); break; + // } + // }; + // $tmp; + // }); + static VertexPtr convert_match_to_switch(VertexAdaptor &match); }; diff --git a/compiler/vertex-desc.json b/compiler/vertex-desc.json index b905cb3aa5..be2491750b 100644 --- a/compiler/vertex-desc.json +++ b/compiler/vertex-desc.json @@ -630,6 +630,19 @@ "str": "case" } }, + { + "comment": "default => $return_expr", + "sons": { + "return_expr" : { + "id": 0 + } + }, + "name": "op_match_default", + "base_name": "meta_op_base", + "props": { + "str": "default" + } + }, { "comment": "if (cond()) true_cmd(); or if (cond()) true_cmd() else false_cmd()", "sons": { From 22e3c12a82dc4852f0614426d8882a5d39469f67 Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Tue, 7 Sep 2021 00:57:04 +0300 Subject: [PATCH 3/9] removed old changes --- compiler/data/vertex-adaptor.h | 2 +- compiler/gentree.cpp | 2 -- compiler/pipes/calc-rl.cpp | 7 +------ compiler/pipes/gen-tree-postprocess.cpp | 23 +++++------------------ 4 files changed, 7 insertions(+), 27 deletions(-) diff --git a/compiler/data/vertex-adaptor.h b/compiler/data/vertex-adaptor.h index b474ec36e5..95f037ad8d 100644 --- a/compiler/data/vertex-adaptor.h +++ b/compiler/data/vertex-adaptor.h @@ -55,7 +55,7 @@ class VertexAdaptor { return *this; } -// VertexAdaptor &operator=(const VertexAdaptor &) & = default; + VertexAdaptor &operator=(const VertexAdaptor &) & = default; explicit operator bool() const { return impl != nullptr; diff --git a/compiler/gentree.cpp b/compiler/gentree.cpp index cb89dbeed0..18a02d61d6 100644 --- a/compiler/gentree.cpp +++ b/compiler/gentree.cpp @@ -660,11 +660,9 @@ VertexPtr GenTree::get_expr_top(bool was_arrow) { case tok_shape: res = get_shape(); break; - case tok_match: res = get_match(); break; - case tok_opbrk: res = get_short_array(); return_flag = false; // array is valid postfix expression operand diff --git a/compiler/pipes/calc-rl.cpp b/compiler/pipes/calc-rl.cpp index 69fd0b1da3..07e1d403b5 100644 --- a/compiler/pipes/calc-rl.cpp +++ b/compiler/pipes/calc-rl.cpp @@ -84,12 +84,7 @@ void rl_other_calc(VertexPtr root, RLValueType expected_rl_type) { case op_list_keyval: { const auto kv = root.as(); rl_calc(kv->key(), val_r); - - if (auto array = kv->var().try_as()) { - rl_calc_all(array, array->size() - 1); - } else { - rl_calc(kv->var(), val_l); - } + rl_calc(kv->var(), val_l); break; } default: diff --git a/compiler/pipes/gen-tree-postprocess.cpp b/compiler/pipes/gen-tree-postprocess.cpp index 4a4a635497..d175f80ad8 100644 --- a/compiler/pipes/gen-tree-postprocess.cpp +++ b/compiler/pipes/gen-tree-postprocess.cpp @@ -41,10 +41,13 @@ VertexAdaptor convert_set_null_coalesce(VertexAdaptor::create(v->lhs().clone(), rhs).set_location(v); } -vector> convert_list_part(VertexPtr list, VertexPtr arr) { +VertexAdaptor make_list_op(VertexAdaptor assign) { bool has_explicit_keys = false; bool has_empty_entries = false; // To give the same error message as PHP + auto list = assign->lhs(); + auto arr = assign->rhs(); + std::vector> mappings; mappings.reserve(list->size()); @@ -53,15 +56,7 @@ vector> convert_list_part(VertexPtr list, VertexPt for (auto x : *list) { if (const auto arrow = x.try_as()) { has_explicit_keys = true; - - if (auto array = arrow->rhs().try_as()) { - const auto rhs_mappings = convert_list_part(array, arr); - const auto array_vertex = VertexAdaptor::create(mappings, arr); - mappings.emplace_back(VertexAdaptor::create(arrow->lhs(), array_vertex)); - } else { - mappings.emplace_back(VertexAdaptor::create(arrow->lhs(), arrow->rhs())); - } - + mappings.emplace_back(VertexAdaptor::create(arrow->lhs(), arrow->rhs())); continue; } @@ -83,14 +78,6 @@ vector> convert_list_part(VertexPtr list, VertexPt } else if (has_explicit_keys && has_implicit_keys) { kphp_error(0, "Cannot mix keyed and unkeyed array entries in assignments"); } - return mappings; -} - -VertexAdaptor make_list_op(VertexAdaptor assign) { - auto list = assign->lhs(); - auto arr = assign->rhs(); - - const auto mappings = convert_list_part(list, arr); return VertexAdaptor::create(mappings, arr).set_location(assign); } From c95ff37827bd0298563f1843ec4a994c143a3bbc Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Tue, 7 Sep 2021 01:13:23 +0300 Subject: [PATCH 4/9] fixed clang compile errors --- compiler/gentree.cpp | 2 +- compiler/pipes/gen-tree-postprocess.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/gentree.cpp b/compiler/gentree.cpp index 18a02d61d6..cae9759e8b 100644 --- a/compiler/gentree.cpp +++ b/compiler/gentree.cpp @@ -1362,7 +1362,7 @@ VertexPtr GenTree::get_match_case() { const auto condition = get_expression(); - if (const auto double_arrow = condition.try_as()) { + if (const auto &double_arrow = condition.try_as()) { // if simple $expr => $return_expr if (conditions.empty()) { const auto return_expr = double_arrow->value(); diff --git a/compiler/pipes/gen-tree-postprocess.cpp b/compiler/pipes/gen-tree-postprocess.cpp index d175f80ad8..e8fe7db6c8 100644 --- a/compiler/pipes/gen-tree-postprocess.cpp +++ b/compiler/pipes/gen-tree-postprocess.cpp @@ -275,15 +275,15 @@ VertexAdaptor create_switch_vertex_from_match(VertexAdaptor VertexAdaptor default_case; - for (const auto case_vertex : cases) { - if (const auto default_case_vertex = case_vertex.try_as()) { + for (const auto &case_vertex : cases) { + if (const auto &default_case_vertex = case_vertex.try_as()) { if (default_case) { kphp_error(0, "Match expressions may only contain one default arm"); } default_case = default_case_vertex; continue; } - if (const auto match_case = case_vertex.try_as()) { + if (const auto &match_case = case_vertex.try_as()) { const auto set_vertex = VertexAdaptor::create(local_var, match_case->return_expr()); const auto break_vertex = VertexAdaptor::create(GenTree::create_int_const(1)); const auto stmts = std::vector{set_vertex, break_vertex}; @@ -302,7 +302,7 @@ VertexAdaptor create_switch_vertex_from_match(VertexAdaptor // case 20: { $a = 1; break; } // } auto current_condition = 0; - for (const auto condition : match_case->conditions()->args()) { + for (const auto &condition : match_case->conditions()->args()) { if (current_condition == condition_count - 1) { switch_cases.emplace_back(VertexAdaptor::create(condition, seq)); continue; From 8fabe2229b57d0afed3309cb2a956d08f556c045 Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Fri, 10 Sep 2021 03:41:11 +0300 Subject: [PATCH 5/9] added `is_match` flag to `op_switch` In PHP, a regular `switch` uses non-strict comparison, which leads to type casts, but the `match` uses strict comparison, which is why we need a new `is_match` flag in the `op_switch` node. If this flag is set, then strict comparison will be used when generating the code for the `switch`. --- compiler/code-gen/vertex-compiler.cpp | 49 ++++++++++++++++++++++++- compiler/pipes/gen-tree-postprocess.cpp | 5 ++- compiler/vertex-desc.json | 6 +++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index d87773d253..0e7a419668 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -943,6 +943,48 @@ void compile_switch_var(VertexAdaptor root, CodeGenerator &W) { W << Label{root->break_label_id}; } +// difference with a regular switch is in strict comparison +void compile_switch_match_var(VertexAdaptor root, CodeGenerator &W) { + string goto_name_if_default_in_the_middle; + + const auto temp_var_condition_on_switch = root->condition_on_switch(); + const auto temp_var_matched_with_a_case = root->matched_with_one_case(); + + W << "do " << BEGIN; + W << temp_var_condition_on_switch << " = " << root->condition() << ";" << NL; + W << temp_var_matched_with_a_case << " = false;" << NL; + + for (const auto &one_case : root->cases()) { + VertexAdaptor cmd; + + if (const auto cs = one_case.try_as()) { + cmd = cs->cmd(); + W << "if (" << temp_var_matched_with_a_case << " || equals(" << temp_var_condition_on_switch << ", " << cs->expr() << "))" << BEGIN; + W << temp_var_matched_with_a_case << " = true;" << NL; + } else { + cmd = one_case.as()->cmd(); + W << "if (" << temp_var_matched_with_a_case << ") " << BEGIN; + goto_name_if_default_in_the_middle = gen_unique_name("switch_goto"); + W << goto_name_if_default_in_the_middle + ": "; + } + + W << AsSeq{cmd}; + W << END << NL; + } + + if (!goto_name_if_default_in_the_middle.empty()) { + W << "if (" << temp_var_matched_with_a_case << ") " << BEGIN; + W << "break;" << NL; + W << END << NL; + + W << temp_var_matched_with_a_case << " = true;" << NL; + W << "goto " << goto_name_if_default_in_the_middle << ";" << NL; + } + + W << Label{root->continue_label_id} << END; + W << " while(false);" << NL; + W << Label{root->break_label_id}; +} void compile_switch(VertexAdaptor root, CodeGenerator &W) { kphp_assert(root->condition_on_switch()->type() == op_var && root->matched_with_one_case()->type() == op_var); @@ -952,7 +994,7 @@ void compile_switch(VertexAdaptor root, CodeGenerator &W) { for (auto one_case : root->cases()) { if (one_case->type() == op_default) { - kphp_error_return(!has_default, "Switch: several `default` cases found"); + kphp_error_return(!has_default, "Switch statements may only contain one `default` clause"); has_default = true; continue; } @@ -962,6 +1004,11 @@ void compile_switch(VertexAdaptor root, CodeGenerator &W) { all_cases_are_strings &= (val->type() == op_string); } + if (root->is_match) { + compile_switch_match_var(root, W); + return; + } + if (all_cases_are_strings) { compile_switch_str(root, W); } else if (all_cases_are_ints) { diff --git a/compiler/pipes/gen-tree-postprocess.cpp b/compiler/pipes/gen-tree-postprocess.cpp index e8fe7db6c8..7f21ab6c85 100644 --- a/compiler/pipes/gen-tree-postprocess.cpp +++ b/compiler/pipes/gen-tree-postprocess.cpp @@ -341,7 +341,10 @@ VertexAdaptor create_switch_vertex_from_match(VertexAdaptor switch_cases.emplace_back(VertexAdaptor::create(seq)); } - return VertexAdaptor::create(switch_condition, temp_var_condition_on_switch, temp_var_matched_with_one_case, std::move(switch_cases)); + auto switch_vertex = VertexAdaptor::create(switch_condition, temp_var_condition_on_switch, temp_var_matched_with_one_case, std::move(switch_cases)); + switch_vertex->is_match = true; + + return switch_vertex; } VertexPtr GenTreePostprocessPass::convert_match_to_switch(VertexAdaptor &match) { diff --git a/compiler/vertex-desc.json b/compiler/vertex-desc.json index be2491750b..a99c6f82c7 100644 --- a/compiler/vertex-desc.json +++ b/compiler/vertex-desc.json @@ -576,6 +576,12 @@ "type": "op_var" } }, + "extra_fields": { + "is_match": { + "type": "bool", + "default": "false" + } + }, "name": "op_switch", "base_name": "meta_op_cycle", "props": { From 6c96121a621bf363277a74d5b1aeb653dac13bdb Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Sun, 12 Sep 2021 22:02:09 +0300 Subject: [PATCH 6/9] update - Fixed incorrect parsing of expressions like "10, => 100". - Improved code generation, now optimized code is generated for only strings and only ints. - Added tests. --- compiler/code-gen/vertex-compiler.cpp | 71 +++++++------------ compiler/gentree.cpp | 19 +++-- tests/cpp/compiler/lexer-test.cpp | 4 ++ tests/phpt/php8/match/001_simple.php | 22 ++++++ tests/phpt/php8/match/002_several_cases.php | 13 ++++ ...003_several_cases_with_different_types.php | 10 +++ tests/phpt/php8/match/004_several_in_case.php | 9 +++ .../php8/match/005_expression_as_case.php | 13 ++++ .../match/006_several_expression_as_case.php | 19 +++++ ...007_string_condition_in_only_int_cases.php | 9 +++ ...008_int_condition_in_only_string_cases.php | 9 +++ tests/phpt/php8/match/009_print_bool.php | 12 ++++ tests/phpt/php8/match/010_default_case.php | 15 ++++ .../php8/match/011_bool_expression_cases.php | 20 ++++++ .../phpt/php8/match/012_discarding_result.php | 11 +++ tests/phpt/php8/match/013_implicit_break.php | 12 ++++ .../php8/match/014_strict_comparisons.php | 29 ++++++++ .../015_strict_comparisons_jump_table.php | 44 ++++++++++++ ...016_strict_comparisons_true_expression.php | 10 +++ ...17_strict_comparisons_false_expression.php | 10 +++ .../match/018_mixed_int_string_jump_table.php | 29 ++++++++ ...9_match_expression_with_trailing_comma.php | 22 ++++++ tests/phpt/php8/match/020_bad_case.php | 7 ++ .../php8/match/021_several_default_arm.php | 8 +++ .../php8/match/022_repeated_conditions.php | 9 +++ .../phpt/php8/match/023_unhandeled_value.php | 8 +++ 26 files changed, 392 insertions(+), 52 deletions(-) create mode 100644 tests/phpt/php8/match/001_simple.php create mode 100644 tests/phpt/php8/match/002_several_cases.php create mode 100644 tests/phpt/php8/match/003_several_cases_with_different_types.php create mode 100644 tests/phpt/php8/match/004_several_in_case.php create mode 100644 tests/phpt/php8/match/005_expression_as_case.php create mode 100644 tests/phpt/php8/match/006_several_expression_as_case.php create mode 100644 tests/phpt/php8/match/007_string_condition_in_only_int_cases.php create mode 100644 tests/phpt/php8/match/008_int_condition_in_only_string_cases.php create mode 100644 tests/phpt/php8/match/009_print_bool.php create mode 100644 tests/phpt/php8/match/010_default_case.php create mode 100644 tests/phpt/php8/match/011_bool_expression_cases.php create mode 100644 tests/phpt/php8/match/012_discarding_result.php create mode 100644 tests/phpt/php8/match/013_implicit_break.php create mode 100644 tests/phpt/php8/match/014_strict_comparisons.php create mode 100644 tests/phpt/php8/match/015_strict_comparisons_jump_table.php create mode 100644 tests/phpt/php8/match/016_strict_comparisons_true_expression.php create mode 100644 tests/phpt/php8/match/017_strict_comparisons_false_expression.php create mode 100644 tests/phpt/php8/match/018_mixed_int_string_jump_table.php create mode 100644 tests/phpt/php8/match/019_match_expression_with_trailing_comma.php create mode 100644 tests/phpt/php8/match/020_bad_case.php create mode 100644 tests/phpt/php8/match/021_several_default_arm.php create mode 100644 tests/phpt/php8/match/022_repeated_conditions.php create mode 100644 tests/phpt/php8/match/023_unhandeled_value.php diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index 0e7a419668..f4976c9a83 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -797,7 +797,7 @@ void compile_switch_str(VertexAdaptor root, CodeGenerator &W) { std::transform(cases_vertices.begin(), cases_vertices.end(), cases.begin(), [](auto v) { return CaseInfo(v); }); auto default_case_it = std::find_if(cases.begin(), cases.end(), vk::make_field_getter(&CaseInfo::is_default)); - auto default_case = default_case_it != cases.end() ? &(*default_case_it) : nullptr; + auto *default_case = default_case_it != cases.end() ? &(*default_case_it) : nullptr; int n = static_cast(cases.size()); std::map hash_to_last_id; @@ -817,7 +817,7 @@ void compile_switch_str(VertexAdaptor root, CodeGenerator &W) { cases[i].next = &cases[next_i]; } - auto next = cases[i].next; + auto *next = cases[i].next; if (next && next->goto_name.empty()) { next->goto_name = gen_unique_name("switch_goto"); } @@ -903,6 +903,9 @@ void compile_switch_int(VertexAdaptor root, CodeGenerator &W) { void compile_switch_var(VertexAdaptor root, CodeGenerator &W) { + const auto strict_comparison = root->is_match; + const auto *comparison = strict_comparison ? "equals" : "eq2"; + string goto_name_if_default_in_the_middle; auto temp_var_condition_on_switch = root->condition_on_switch(); @@ -916,7 +919,7 @@ void compile_switch_var(VertexAdaptor root, CodeGenerator &W) { VertexAdaptor cmd; if (auto cs = one_case.try_as()) { cmd = cs->cmd(); - W << "if (" << temp_var_matched_with_a_case << " || eq2(" << temp_var_condition_on_switch << ", " << cs->expr() << "))" << BEGIN; + W << "if (" << temp_var_matched_with_a_case << " || " << comparison << "(" << temp_var_condition_on_switch << ", " << cs->expr() << "))" << BEGIN; W << temp_var_matched_with_a_case << " = true;" << NL; } else { cmd = one_case.as()->cmd(); @@ -943,48 +946,6 @@ void compile_switch_var(VertexAdaptor root, CodeGenerator &W) { W << Label{root->break_label_id}; } -// difference with a regular switch is in strict comparison -void compile_switch_match_var(VertexAdaptor root, CodeGenerator &W) { - string goto_name_if_default_in_the_middle; - - const auto temp_var_condition_on_switch = root->condition_on_switch(); - const auto temp_var_matched_with_a_case = root->matched_with_one_case(); - - W << "do " << BEGIN; - W << temp_var_condition_on_switch << " = " << root->condition() << ";" << NL; - W << temp_var_matched_with_a_case << " = false;" << NL; - - for (const auto &one_case : root->cases()) { - VertexAdaptor cmd; - - if (const auto cs = one_case.try_as()) { - cmd = cs->cmd(); - W << "if (" << temp_var_matched_with_a_case << " || equals(" << temp_var_condition_on_switch << ", " << cs->expr() << "))" << BEGIN; - W << temp_var_matched_with_a_case << " = true;" << NL; - } else { - cmd = one_case.as()->cmd(); - W << "if (" << temp_var_matched_with_a_case << ") " << BEGIN; - goto_name_if_default_in_the_middle = gen_unique_name("switch_goto"); - W << goto_name_if_default_in_the_middle + ": "; - } - - W << AsSeq{cmd}; - W << END << NL; - } - - if (!goto_name_if_default_in_the_middle.empty()) { - W << "if (" << temp_var_matched_with_a_case << ") " << BEGIN; - W << "break;" << NL; - W << END << NL; - - W << temp_var_matched_with_a_case << " = true;" << NL; - W << "goto " << goto_name_if_default_in_the_middle << ";" << NL; - } - - W << Label{root->continue_label_id} << END; - W << " while(false);" << NL; - W << Label{root->break_label_id}; -} void compile_switch(VertexAdaptor root, CodeGenerator &W) { kphp_assert(root->condition_on_switch()->type() == op_var && root->matched_with_one_case()->type() == op_var); @@ -1005,7 +966,25 @@ void compile_switch(VertexAdaptor root, CodeGenerator &W) { } if (root->is_match) { - compile_switch_match_var(root, W); + // Since match uses strict comparisons, we need to additionally + // check that the type of the expression in the condition matches + // string or int in order to generate an optimized version. + const auto* condition_type = tinf::get_type(root->condition()); + if (!condition_type) { + compile_switch_var(root, W); + return; + } + + const auto condition_is_string = condition_type->get_real_ptype() == tp_string; + const auto condition_is_int = condition_type->get_real_ptype() == tp_int; + + if (all_cases_are_strings && condition_is_string) { + compile_switch_str(root, W); + } else if (all_cases_are_ints && condition_is_int) { + compile_switch_int(root, W); + } else { + compile_switch_var(root, W); + } return; } diff --git a/compiler/gentree.cpp b/compiler/gentree.cpp index cae9759e8b..e3a6f179e5 100644 --- a/compiler/gentree.cpp +++ b/compiler/gentree.cpp @@ -1345,12 +1345,12 @@ VertexAdaptor GenTree::get_match_default() { VertexPtr GenTree::get_match_case() { auto location = auto_location(); - std::vector conditions; - if (cur->type() == tok_default) { return get_match_default(); } + std::vector conditions; + while (cur->type() != tok_double_arrow) { const auto cur_type = cur->type(); // '$expr, => $return' @@ -1361,6 +1361,9 @@ VertexPtr GenTree::get_match_case() { } const auto condition = get_expression(); + if (!condition) { + kphp_error(0, "Could not parse expression"); + } if (const auto &double_arrow = condition.try_as()) { // if simple $expr => $return_expr @@ -1384,11 +1387,15 @@ VertexPtr GenTree::get_match_case() { conditions.push_back(condition); } CE (expect(tok_double_arrow, "'=>'")); + // case " => 100" + if (conditions.empty()) { + kphp_error(0, "Expected expression before token '=>'"); + } - // Reachable only if arm does not contain '$expr => $return_expr'. - // Note that arm '$expr1, $expr2 => $return_expr' contains '$expr => $return_expr'. - kphp_error(0, "Bad match arm"); - return VertexPtr{}; + const auto return_expr = get_expression(); + const auto conditions_vertex = VertexAdaptor::create(conditions); + + return VertexAdaptor::create(conditions_vertex, return_expr).set_location(location); } VertexAdaptor GenTree::get_match() { diff --git a/tests/cpp/compiler/lexer-test.cpp b/tests/cpp/compiler/lexer-test.cpp index 5a437b887a..e6a35e31a0 100644 --- a/tests/cpp/compiler/lexer-test.cpp +++ b/tests/cpp/compiler/lexer-test.cpp @@ -113,6 +113,10 @@ TEST(lexer_test, test_php_tokens) { // combined tests {"echo \"{$x->y}\";", {"tok_echo(echo)", "tok_str_begin(\")", "tok_expr_begin({)", "tok_var_name($x)", "tok_arrow(->)", "tok_func_name(y)", "tok_expr_end(})", "tok_str_end(\")", "tok_semicolon(;)"}}, + + // match expression + {"match($a) { 10 => 100, default => 1 };", {"tok_match(match)", "tok_oppar(()", "tok_var_name($a)", "tok_clpar())", "tok_opbrc({)", "tok_int_const(10)", "tok_double_arrow(=>)", "tok_int_const(100)", "tok_comma(,)", "tok_default(default)", "tok_double_arrow(=>)", "tok_int_const(1)", "tok_clbrc(})", "tok_semicolon(;)"}}, + {"match($a) { 10, 11, 12, => 100,};", {"tok_match(match)", "tok_oppar(()", "tok_var_name($a)", "tok_clpar())", "tok_opbrc({)", "tok_int_const(10)", "tok_comma(,)", "tok_int_const(11)", "tok_comma(,)", "tok_int_const(12)", "tok_comma(,)", "tok_double_arrow(=>)", "tok_int_const(100)", "tok_comma(,)", "tok_clbrc(})", "tok_semicolon(;)"}} }; for (const auto &test : tests) { diff --git a/tests/phpt/php8/match/001_simple.php b/tests/phpt/php8/match/001_simple.php new file mode 100644 index 0000000000..2b0018fe0f --- /dev/null +++ b/tests/phpt/php8/match/001_simple.php @@ -0,0 +1,22 @@ +@ok php8 + 'Zero', + 1 => 'One', + 2 => 'Two', + 3 => 'Three', + 4 => 'Four', + 5 => 'Five', + 6 => 'Six', + 7 => 'Seven', + 8 => 'Eight', + 9 => 'Nine', + default => 'default', + }; +} + +for ($i = 0; $i <= 9; $i++) { + echo wordify($i) . "\n"; +} diff --git a/tests/phpt/php8/match/002_several_cases.php b/tests/phpt/php8/match/002_several_cases.php new file mode 100644 index 0000000000..141c891f05 --- /dev/null +++ b/tests/phpt/php8/match/002_several_cases.php @@ -0,0 +1,13 @@ +@ok php8 + false, + 2, 3, 4, 5, 6 => true, + }; +} + +for ($i = 1; $i <= 7; $i++) { + var_dump(is_working_day($i)); +} diff --git a/tests/phpt/php8/match/003_several_cases_with_different_types.php b/tests/phpt/php8/match/003_several_cases_with_different_types.php new file mode 100644 index 0000000000..d3f278ea4a --- /dev/null +++ b/tests/phpt/php8/match/003_several_cases_with_different_types.php @@ -0,0 +1,10 @@ +@ok php8 + "oops, error", + "hello" => "ok", + default => "oops, default", +}); diff --git a/tests/phpt/php8/match/004_several_in_case.php b/tests/phpt/php8/match/004_several_in_case.php new file mode 100644 index 0000000000..cf43bb891a --- /dev/null +++ b/tests/phpt/php8/match/004_several_in_case.php @@ -0,0 +1,9 @@ +@ok php8 + "ok", + default => "oops, default", +}); diff --git a/tests/phpt/php8/match/005_expression_as_case.php b/tests/phpt/php8/match/005_expression_as_case.php new file mode 100644 index 0000000000..559cc08403 --- /dev/null +++ b/tests/phpt/php8/match/005_expression_as_case.php @@ -0,0 +1,13 @@ +@ok php8 + "ok", + default => "oops, default", +}); diff --git a/tests/phpt/php8/match/006_several_expression_as_case.php b/tests/phpt/php8/match/006_several_expression_as_case.php new file mode 100644 index 0000000000..b2a384a66f --- /dev/null +++ b/tests/phpt/php8/match/006_several_expression_as_case.php @@ -0,0 +1,19 @@ +@ok php8 + "ok", + f2() => "oops", + default => "oops, default", +}); diff --git a/tests/phpt/php8/match/007_string_condition_in_only_int_cases.php b/tests/phpt/php8/match/007_string_condition_in_only_int_cases.php new file mode 100644 index 0000000000..db56422e75 --- /dev/null +++ b/tests/phpt/php8/match/007_string_condition_in_only_int_cases.php @@ -0,0 +1,9 @@ +@ok php8 + "oops, type casting", + default => "ok", +}); diff --git a/tests/phpt/php8/match/008_int_condition_in_only_string_cases.php b/tests/phpt/php8/match/008_int_condition_in_only_string_cases.php new file mode 100644 index 0000000000..e67ba96c5f --- /dev/null +++ b/tests/phpt/php8/match/008_int_condition_in_only_string_cases.php @@ -0,0 +1,9 @@ +@ok php8 + "oops, type casting", + default => "ok", +}); diff --git a/tests/phpt/php8/match/009_print_bool.php b/tests/phpt/php8/match/009_print_bool.php new file mode 100644 index 0000000000..1289c69477 --- /dev/null +++ b/tests/phpt/php8/match/009_print_bool.php @@ -0,0 +1,12 @@ +@ok php8 + "true\n", + false => "false\n" + }; +} + +print_bool(true); +print_bool(false); diff --git a/tests/phpt/php8/match/010_default_case.php b/tests/phpt/php8/match/010_default_case.php new file mode 100644 index 0000000000..62a2c22c4e --- /dev/null +++ b/tests/phpt/php8/match/010_default_case.php @@ -0,0 +1,15 @@ +@ok php8 + 1, + 2 => 2, + default => 'default', + }; +} + +echo get_value(0) . "\n"; +echo get_value(1) . "\n"; +echo get_value(2) . "\n"; +echo get_value(3) . "\n"; diff --git a/tests/phpt/php8/match/011_bool_expression_cases.php b/tests/phpt/php8/match/011_bool_expression_cases.php new file mode 100644 index 0000000000..5ca7d97490 --- /dev/null +++ b/tests/phpt/php8/match/011_bool_expression_cases.php @@ -0,0 +1,20 @@ +@ok php8 += 50 => '50+', + $i >= 40 => '40-50', + $i >= 30 => '30-40', + $i >= 20 => '20-30', + $i >= 10 => '10-20', + default => '0-10', + }; +} + +echo get_range(22) . "\n"; +echo get_range(0) . "\n"; +echo get_range(59) . "\n"; +echo get_range(13) . "\n"; +echo get_range(39) . "\n"; +echo get_range(40) . "\n"; diff --git a/tests/phpt/php8/match/012_discarding_result.php b/tests/phpt/php8/match/012_discarding_result.php new file mode 100644 index 0000000000..3f3a289a3f --- /dev/null +++ b/tests/phpt/php8/match/012_discarding_result.php @@ -0,0 +1,11 @@ +@ok php8 + print_this(), +}; diff --git a/tests/phpt/php8/match/013_implicit_break.php b/tests/phpt/php8/match/013_implicit_break.php new file mode 100644 index 0000000000..36f135cc92 --- /dev/null +++ b/tests/phpt/php8/match/013_implicit_break.php @@ -0,0 +1,12 @@ +@ok php8 + dump_and_return('foo'), + 'bar' => dump_and_return('bar'), +}); diff --git a/tests/phpt/php8/match/014_strict_comparisons.php b/tests/phpt/php8/match/014_strict_comparisons.php new file mode 100644 index 0000000000..cc95ee6f36 --- /dev/null +++ b/tests/phpt/php8/match/014_strict_comparisons.php @@ -0,0 +1,29 @@ +@ok php8 + wrong(), + false => wrong(), + 0.0 => wrong(), + [] => wrong(), + '' => wrong(), + 0 => 'int', +}); + +function get_value2() { + return 0; +} + +var_dump(match (get_value2()) { + null => wrong(), + false => wrong(), + 0.0 => wrong(), + [] => wrong(), + '' => wrong(), + 0 => 'int', + default => 'default', +}); diff --git a/tests/phpt/php8/match/015_strict_comparisons_jump_table.php b/tests/phpt/php8/match/015_strict_comparisons_jump_table.php new file mode 100644 index 0000000000..3f31f18b0c --- /dev/null +++ b/tests/phpt/php8/match/015_strict_comparisons_jump_table.php @@ -0,0 +1,44 @@ +@ok php8 + wrong(), + 1 => wrong(), + 2 => wrong(), + 3 => wrong(), + 4 => wrong(), + 5 => wrong(), + 6 => wrong(), + 7 => wrong(), + 8 => wrong(), + 9 => wrong(), + default => 'Not matched', + }; +} + +foreach (range(0, 9) as $int) { + var_dump((string)$int); + var_dump(test_int((string)$int)); +} + +function test_string($int) { + return match ($int) { + '0' => wrong(), + '1' => wrong(), + '2' => wrong(), + '3' => wrong(), + '4' => wrong(), + '5' => wrong(), + '6' => wrong(), + '7' => wrong(), + '8' => wrong(), + '9' => wrong(), + default => 'Not matched', + }; +} + +foreach (range(0, 9) as $int) { + var_dump($int); + var_dump(test_string($int)); +} diff --git a/tests/phpt/php8/match/016_strict_comparisons_true_expression.php b/tests/phpt/php8/match/016_strict_comparisons_true_expression.php new file mode 100644 index 0000000000..119c4beab7 --- /dev/null +++ b/tests/phpt/php8/match/016_strict_comparisons_true_expression.php @@ -0,0 +1,10 @@ +@ok php8 + wrong(), + ['truthy'] => wrong(), + 1 => wrong(), + 1.0 => wrong(), + true => "true\n", +}; diff --git a/tests/phpt/php8/match/017_strict_comparisons_false_expression.php b/tests/phpt/php8/match/017_strict_comparisons_false_expression.php new file mode 100644 index 0000000000..978e55b85f --- /dev/null +++ b/tests/phpt/php8/match/017_strict_comparisons_false_expression.php @@ -0,0 +1,10 @@ +@ok php8 + wrong(), + [] => wrong(), + 0 => wrong(), + 0.0 => wrong(), + false => "false\n", +}; diff --git a/tests/phpt/php8/match/018_mixed_int_string_jump_table.php b/tests/phpt/php8/match/018_mixed_int_string_jump_table.php new file mode 100644 index 0000000000..ea8fcd4521 --- /dev/null +++ b/tests/phpt/php8/match/018_mixed_int_string_jump_table.php @@ -0,0 +1,29 @@ +@ok php8 + '1 int', + '1' => '1 string', + 2 => '2 int', + '2' => '2 string', + 3 => '3 int', + '3' => '3 string', + 4 => '4 int', + '4' => '4 string', + 5 => '5 int', + '5' => '5 string', + }; + echo "\n"; +} + +test(1); +test('1'); +test(2); +test('2'); +test(3); +test('3'); +test(4); +test('4'); +test(5); +test('5'); diff --git a/tests/phpt/php8/match/019_match_expression_with_trailing_comma.php b/tests/phpt/php8/match/019_match_expression_with_trailing_comma.php new file mode 100644 index 0000000000..d6dbc50e36 --- /dev/null +++ b/tests/phpt/php8/match/019_match_expression_with_trailing_comma.php @@ -0,0 +1,22 @@ +@ok php8 + "false\n", + true, + 1, + => "true\n", + default, + => "not bool\n", + }; +} + +print_bool(false); +print_bool(0); +print_bool(true); +print_bool(1); +print_bool(2); +print_bool('foo'); diff --git a/tests/phpt/php8/match/020_bad_case.php b/tests/phpt/php8/match/020_bad_case.php new file mode 100644 index 0000000000..4e9e58ffa8 --- /dev/null +++ b/tests/phpt/php8/match/020_bad_case.php @@ -0,0 +1,7 @@ +@kphp_should_fail php8 +/Expected expression before token '=>'/ + "ok", +}; diff --git a/tests/phpt/php8/match/021_several_default_arm.php b/tests/phpt/php8/match/021_several_default_arm.php new file mode 100644 index 0000000000..a6f2898713 --- /dev/null +++ b/tests/phpt/php8/match/021_several_default_arm.php @@ -0,0 +1,8 @@ +@kphp_should_fail php8 +/Match expressions may only contain one default arm/ + "default", + default => "default", +}; diff --git a/tests/phpt/php8/match/022_repeated_conditions.php b/tests/phpt/php8/match/022_repeated_conditions.php new file mode 100644 index 0000000000..e916b4f8c8 --- /dev/null +++ b/tests/phpt/php8/match/022_repeated_conditions.php @@ -0,0 +1,9 @@ +@kphp_should_fail php8 +/Switch: repeated cases found \[10\]/ + "1", + 10 => "2", + default => "default", +}; diff --git a/tests/phpt/php8/match/023_unhandeled_value.php b/tests/phpt/php8/match/023_unhandeled_value.php new file mode 100644 index 0000000000..a16a939dec --- /dev/null +++ b/tests/phpt/php8/match/023_unhandeled_value.php @@ -0,0 +1,8 @@ +@kphp_runtime_should_warn php8 +/Unhandled match value '100'/ + "1", + 30 => "2", +}; From 2c7e51380d75314450dd718e3fb6582df4cb89f1 Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Sun, 12 Sep 2021 22:13:42 +0300 Subject: [PATCH 7/9] fixed minor issues --- compiler/code-gen/vertex-compiler.cpp | 8 ++++---- tests/phpt/php8/match/022_repeated_conditions.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index f4976c9a83..b4feeb641b 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -885,7 +885,7 @@ void compile_switch_int(VertexAdaptor root, CodeGenerator &W) { if (val->type() == op_int_const) { const string &str = val.as()->str_val; W << str; - kphp_error(used.insert(str).second, fmt_format("Switch: repeated cases found [{}]", str)); + kphp_error(used.insert(str).second, fmt_format("Repeated case [{}] in switch/match", str)); } else { kphp_assert(is_const_int(val)); W << val; @@ -904,7 +904,7 @@ void compile_switch_int(VertexAdaptor root, CodeGenerator &W) { void compile_switch_var(VertexAdaptor root, CodeGenerator &W) { const auto strict_comparison = root->is_match; - const auto *comparison = strict_comparison ? "equals" : "eq2"; + const auto *string_comparison_func = strict_comparison ? "equals" : "eq2"; string goto_name_if_default_in_the_middle; @@ -919,7 +919,7 @@ void compile_switch_var(VertexAdaptor root, CodeGenerator &W) { VertexAdaptor cmd; if (auto cs = one_case.try_as()) { cmd = cs->cmd(); - W << "if (" << temp_var_matched_with_a_case << " || " << comparison << "(" << temp_var_condition_on_switch << ", " << cs->expr() << "))" << BEGIN; + W << "if (" << temp_var_matched_with_a_case << " || " << string_comparison_func << "(" << temp_var_condition_on_switch << ", " << cs->expr() << "))" << BEGIN; W << temp_var_matched_with_a_case << " = true;" << NL; } else { cmd = one_case.as()->cmd(); @@ -969,7 +969,7 @@ void compile_switch(VertexAdaptor root, CodeGenerator &W) { // Since match uses strict comparisons, we need to additionally // check that the type of the expression in the condition matches // string or int in order to generate an optimized version. - const auto* condition_type = tinf::get_type(root->condition()); + const auto *condition_type = tinf::get_type(root->condition()); if (!condition_type) { compile_switch_var(root, W); return; diff --git a/tests/phpt/php8/match/022_repeated_conditions.php b/tests/phpt/php8/match/022_repeated_conditions.php index e916b4f8c8..7ddcf4c847 100644 --- a/tests/phpt/php8/match/022_repeated_conditions.php +++ b/tests/phpt/php8/match/022_repeated_conditions.php @@ -1,5 +1,5 @@ @kphp_should_fail php8 -/Switch: repeated cases found \[10\]/ +/Repeated case \[10\] in switch\/match/ Date: Mon, 13 Sep 2021 16:36:28 +0300 Subject: [PATCH 8/9] fixed tests --- tests/phpt/php8/match/015_strict_comparisons_jump_table.php | 4 ++++ .../php8/match/016_strict_comparisons_true_expression.php | 4 ++++ .../php8/match/017_strict_comparisons_false_expression.php | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/tests/phpt/php8/match/015_strict_comparisons_jump_table.php b/tests/phpt/php8/match/015_strict_comparisons_jump_table.php index 3f31f18b0c..3206638307 100644 --- a/tests/phpt/php8/match/015_strict_comparisons_jump_table.php +++ b/tests/phpt/php8/match/015_strict_comparisons_jump_table.php @@ -1,6 +1,10 @@ @ok php8 wrong(), diff --git a/tests/phpt/php8/match/016_strict_comparisons_true_expression.php b/tests/phpt/php8/match/016_strict_comparisons_true_expression.php index 119c4beab7..9f0fcf4fd9 100644 --- a/tests/phpt/php8/match/016_strict_comparisons_true_expression.php +++ b/tests/phpt/php8/match/016_strict_comparisons_true_expression.php @@ -1,6 +1,10 @@ @ok php8 wrong(), ['truthy'] => wrong(), diff --git a/tests/phpt/php8/match/017_strict_comparisons_false_expression.php b/tests/phpt/php8/match/017_strict_comparisons_false_expression.php index 978e55b85f..1e256d6364 100644 --- a/tests/phpt/php8/match/017_strict_comparisons_false_expression.php +++ b/tests/phpt/php8/match/017_strict_comparisons_false_expression.php @@ -1,6 +1,10 @@ @ok php8 wrong(), [] => wrong(), From 38e15b6019ee02d1484b07343525868a2602bd77 Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Tue, 14 Sep 2021 18:12:47 +0300 Subject: [PATCH 9/9] fixed handling unhandeled arrays --- compiler/pipes/gen-tree-postprocess.cpp | 7 ++++++- .../phpt/php8/match/024_unhandeled_array_value.php | 14 ++++++++++++++ tests/phpt/php8/match/025_arrays.php | 13 +++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 tests/phpt/php8/match/024_unhandeled_array_value.php create mode 100644 tests/phpt/php8/match/025_arrays.php diff --git a/compiler/pipes/gen-tree-postprocess.cpp b/compiler/pipes/gen-tree-postprocess.cpp index 7f21ab6c85..6c4ea18d39 100644 --- a/compiler/pipes/gen-tree-postprocess.cpp +++ b/compiler/pipes/gen-tree-postprocess.cpp @@ -327,9 +327,14 @@ VertexAdaptor create_switch_vertex_from_match(VertexAdaptor // // PHP throws an UnhandledMatchError if the value is unhandled, // but we only give warning. + auto print_r = VertexAdaptor::create(std::vector{ + match->condition().clone(), + VertexAdaptor::create() + }); + print_r->set_string("print_r"); const auto message = VertexAdaptor::create(std::vector{ GenTree::create_string_const("Unhandled match value '"), - match->condition().clone(), + print_r, GenTree::create_string_const("'") }); const auto args = std::vector{message}; diff --git a/tests/phpt/php8/match/024_unhandeled_array_value.php b/tests/phpt/php8/match/024_unhandeled_array_value.php new file mode 100644 index 0000000000..14c7509276 --- /dev/null +++ b/tests/phpt/php8/match/024_unhandeled_array_value.php @@ -0,0 +1,14 @@ +@kphp_runtime_should_warn php8 +/Unhandled match value 'Array/ + 1, + ['yearly', 'credit-card'] => 2, + ['monthly', 'credit-card'] => 3, +}; diff --git a/tests/phpt/php8/match/025_arrays.php b/tests/phpt/php8/match/025_arrays.php new file mode 100644 index 0000000000..a6c5290d47 --- /dev/null +++ b/tests/phpt/php8/match/025_arrays.php @@ -0,0 +1,13 @@ +@ok php8 + 1, + ['yearly', 'credit-card'] => 2, + ['monthly', 'credit-card'] => 3, +};