From f42e2f4212b2a14f425ad12d97ae6306f43b5991 Mon Sep 17 00:00:00 2001 From: "vyacheslav.tsygankov" Date: Wed, 12 Jul 2023 12:12:54 +0300 Subject: [PATCH] Added errors for unimplemented features --- compiler/code-gen/vertex-compiler.cpp | 6 + compiler/debug.cpp | 6 + compiler/gentree.cpp | 120 +++++++++++++++--- compiler/gentree.h | 3 +- compiler/inferring/expr-node.cpp | 13 +- compiler/inferring/primitive-type.cpp | 1 + compiler/inferring/primitive-type.h | 1 + compiler/inferring/type-data.cpp | 4 + compiler/keywords.gperf | 5 + compiler/lexer.cpp | 29 +++-- compiler/phpdoc.cpp | 17 ++- compiler/pipes/cfg.cpp | 4 + compiler/pipes/convert-list-assignments.cpp | 3 + compiler/pipes/fix-returns.cpp | 3 + compiler/pipes/gen-tree-postprocess.cpp | 8 ++ compiler/token.h | 6 + compiler/vertex-desc.json | 56 +++++++- tests/cpp/compiler/lexer-test.cpp | 22 +++- tests/phpt/cl/200_readonly_class.php | 5 + tests/phpt/cl/201_readonly_final_class.php | 5 + tests/phpt/cl/202_final_readonly_class.php | 5 + tests/phpt/cl/203_readonly_abstract_class.php | 5 + tests/phpt/cl/204_abstract_readonly_class.php | 5 + .../cl/205_first_class_callable_syntax.php | 6 + .../cl/206_first_class_callable_syntax.php | 6 + .../cl/207_first_class_callable_syntax.php | 12 ++ .../cl/208_first_class_callable_syntax.php | 10 ++ .../cl/209_first_class_callable_syntax.php | 6 + .../cl/210_first_class_callable_syntax.php | 5 + .../cl/211_first_class_callable_syntax.php | 5 + .../cl/212_first_class_callable_syntax.php | 5 + tests/phpt/cl/213_fail_true_type_in_class.php | 18 +++ ...14_fail_iterable_pseudo-type_narrowing.php | 17 +++ .../constants/Traits/01_const_in_trait.php | 19 +++ .../01_try_multicatch.php | 12 ++ .../02_try_multicatch.php | 15 +++ .../03_try_multicatch.php | 17 +++ .../throw_expression/01_fail_throw_expr.php | 9 ++ .../throw_expression/02_fail_throw_expr.php | 9 ++ .../throw_expression/03_fail_throw_expr.php | 10 ++ .../throw_expression/04_fail_throw_expr.php | 10 ++ .../throw_expression/05_fail_throw_expr.php | 12 ++ .../throw_expression/06_ok_throw_expr.php | 8 ++ .../with_finally/01_finally_empty.php | 11 ++ tests/phpt/list/01_fail_links_in_list.php | 7 + tests/phpt/list/02_fail_links_in_list.php | 7 + .../001_numeric_literal_ok.php | 13 ++ .../201_param_fail_intersection_type.php | 13 ++ .../202_return_fail_intersection_type.php | 13 ++ .../phpt/phpdocs/203_param_fail_true_type.php | 8 ++ .../phpdocs/204_return_fail_true_type.php | 8 ++ .../205_param_fail_iterable_pseudo-type.php | 9 ++ .../206_return_fail_iterable_pseudo-type.php | 8 ++ .../params/09_fail_intersection_type.php | 12 ++ .../typehints/params/10_fail_true_type.php | 7 + .../params/11_fail_iterable_pseudo-type.php | 8 ++ .../params/12_fail_iterable_pseudo-type.php | 10 ++ .../16_fail_intersection_type.php | 12 ++ .../return_types/17_fail_true_type.php | 7 + .../18_fail_iterable_pseudo-type.php | 7 + tests/phpt/yield/01_fail_yield.php | 13 ++ tests/phpt/yield/02_fail_yield.php | 11 ++ tests/phpt/yield/03_fail_yield.php | 10 ++ tests/phpt/yield/04_fail_yield.php | 15 +++ tests/phpt/yield/05_fail_yield.php | 27 ++++ 65 files changed, 746 insertions(+), 33 deletions(-) create mode 100644 tests/phpt/cl/200_readonly_class.php create mode 100644 tests/phpt/cl/201_readonly_final_class.php create mode 100644 tests/phpt/cl/202_final_readonly_class.php create mode 100644 tests/phpt/cl/203_readonly_abstract_class.php create mode 100644 tests/phpt/cl/204_abstract_readonly_class.php create mode 100644 tests/phpt/cl/205_first_class_callable_syntax.php create mode 100644 tests/phpt/cl/206_first_class_callable_syntax.php create mode 100644 tests/phpt/cl/207_first_class_callable_syntax.php create mode 100644 tests/phpt/cl/208_first_class_callable_syntax.php create mode 100644 tests/phpt/cl/209_first_class_callable_syntax.php create mode 100644 tests/phpt/cl/210_first_class_callable_syntax.php create mode 100644 tests/phpt/cl/211_first_class_callable_syntax.php create mode 100644 tests/phpt/cl/212_first_class_callable_syntax.php create mode 100644 tests/phpt/cl/213_fail_true_type_in_class.php create mode 100644 tests/phpt/cl/214_fail_iterable_pseudo-type_narrowing.php create mode 100644 tests/phpt/constants/Traits/01_const_in_trait.php create mode 100644 tests/phpt/exceptions/catching_multiple_exception_types/01_try_multicatch.php create mode 100644 tests/phpt/exceptions/catching_multiple_exception_types/02_try_multicatch.php create mode 100644 tests/phpt/exceptions/catching_multiple_exception_types/03_try_multicatch.php create mode 100644 tests/phpt/exceptions/throw_expression/01_fail_throw_expr.php create mode 100644 tests/phpt/exceptions/throw_expression/02_fail_throw_expr.php create mode 100644 tests/phpt/exceptions/throw_expression/03_fail_throw_expr.php create mode 100644 tests/phpt/exceptions/throw_expression/04_fail_throw_expr.php create mode 100644 tests/phpt/exceptions/throw_expression/05_fail_throw_expr.php create mode 100644 tests/phpt/exceptions/throw_expression/06_ok_throw_expr.php create mode 100644 tests/phpt/exceptions/with_finally/01_finally_empty.php create mode 100644 tests/phpt/list/01_fail_links_in_list.php create mode 100644 tests/phpt/list/02_fail_links_in_list.php create mode 100644 tests/phpt/phpdocs/201_param_fail_intersection_type.php create mode 100644 tests/phpt/phpdocs/202_return_fail_intersection_type.php create mode 100644 tests/phpt/phpdocs/203_param_fail_true_type.php create mode 100644 tests/phpt/phpdocs/204_return_fail_true_type.php create mode 100644 tests/phpt/phpdocs/205_param_fail_iterable_pseudo-type.php create mode 100644 tests/phpt/phpdocs/206_return_fail_iterable_pseudo-type.php create mode 100644 tests/phpt/typehints/params/09_fail_intersection_type.php create mode 100644 tests/phpt/typehints/params/10_fail_true_type.php create mode 100644 tests/phpt/typehints/params/11_fail_iterable_pseudo-type.php create mode 100644 tests/phpt/typehints/params/12_fail_iterable_pseudo-type.php create mode 100644 tests/phpt/typehints/return_types/16_fail_intersection_type.php create mode 100644 tests/phpt/typehints/return_types/17_fail_true_type.php create mode 100644 tests/phpt/typehints/return_types/18_fail_iterable_pseudo-type.php create mode 100644 tests/phpt/yield/01_fail_yield.php create mode 100644 tests/phpt/yield/02_fail_yield.php create mode 100644 tests/phpt/yield/03_fail_yield.php create mode 100644 tests/phpt/yield/04_fail_yield.php create mode 100644 tests/phpt/yield/05_fail_yield.php diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index df8678ac3e..287f6ac586 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -297,6 +297,12 @@ void compile_throw(VertexAdaptor root, CodeGenerator &W) { } void compile_try(VertexAdaptor root, CodeGenerator &W) { + bool is_exist_finally = root->finally_cmd_ref()->type() != Operation::op_empty; + if (is_exist_finally) { + stage::set_location(root->finally_cmd()->location); + kphp_error(0, "`finally` construct is not implemented"); + } + auto move_exception = [&](ClassPtr caught_class, VertexAdaptor dst) { if (caught_class->name == "Throwable") { W << dst << " = std::move(CurException);" << NL; diff --git a/compiler/debug.cpp b/compiler/debug.cpp index 795ed12fd9..6634bea9a7 100644 --- a/compiler/debug.cpp +++ b/compiler/debug.cpp @@ -58,6 +58,7 @@ std::string debugTokenName(TokenType t) { {tok_array, "tok_array"}, {tok_tuple, "tok_tuple"}, {tok_shape, "tok_shape"}, + {tok_iterable, "tok_iterable"}, {tok_as, "tok_as"}, {tok_case, "tok_case"}, {tok_switch, "tok_switch"}, @@ -73,6 +74,8 @@ std::string debugTokenName(TokenType t) { {tok_do, "tok_do"}, {tok_eval, "tok_eval"}, {tok_return, "tok_return"}, + {tok_yield, "tok_yield"}, + {tok_from, "tok_from"}, {tok_list, "tok_list"}, {tok_include, "tok_include"}, {tok_include_once, "tok_include_once"}, @@ -85,6 +88,7 @@ std::string debugTokenName(TokenType t) { {tok_static, "tok_static"}, {tok_final, "tok_final"}, {tok_abstract, "tok_abstract"}, + {tok_readonly, "tok_readonly"}, {tok_goto, "tok_goto"}, {tok_isset, "tok_isset"}, {tok_declare, "tok_declare"}, @@ -176,6 +180,7 @@ std::string debugTokenName(TokenType t) { {tok_conv_array, "tok_conv_array"}, {tok_conv_object, "tok_conv_object"}, {tok_conv_bool, "tok_conv_bool"}, + {tok_conv_iterable, "tok_conv_iterable"}, {tok_false, "tok_false"}, {tok_true, "tok_true"}, {tok_define, "tok_define"}, @@ -185,6 +190,7 @@ std::string debugTokenName(TokenType t) { {tok_new, "tok_new"}, {tok_try, "tok_try"}, {tok_catch, "tok_catch"}, + {tok_finally, "tok_finally"}, {tok_public, "tok_public"}, {tok_private, "tok_private"}, {tok_protected, "tok_protected"}, diff --git a/compiler/gentree.cpp b/compiler/gentree.cpp index 8dc0fa7afe..992b2b59e3 100644 --- a/compiler/gentree.cpp +++ b/compiler/gentree.cpp @@ -534,14 +534,25 @@ VertexPtr GenTree::get_expr_top(bool was_arrow, const PhpDocComment *phpdoc) { break; } case tok_varg: { - bool good_prefix = cur != tokens.begin() && vk::any_of_equal(std::prev(cur)->type(), tok_comma, tok_oppar, tok_opbrk); + auto prev_tok_type = std::prev(cur)->type(); + bool good_prefix = cur != tokens.begin() && vk::any_of_equal(prev_tok_type, tok_comma, tok_oppar, tok_opbrk); CE (!kphp_error(good_prefix, "It's not allowed using `...` in this place")); next_cur(); + auto next_tok_type = cur->type(); // next relative to tok_varg res = get_expression(); // since the argument for the spread operator can be anything, // we do not check the type of the expression here - res = VertexAdaptor::create(res).set_location(res); + if (res) { + res = VertexAdaptor::create(res).set_location(res); + } else { + if (prev_tok_type == tok_oppar && next_tok_type == tok_clpar) { // f(...) - only this syntax is possible + res = VertexAdaptor::create(); + kphp_error(0, "First class callable syntax is not supported"); + } else { + kphp_error(0, "Сan not parse first class callable syntax"); + } + } break; } case tok_str: @@ -625,6 +636,12 @@ VertexPtr GenTree::get_expr_top(bool was_arrow, const PhpDocComment *phpdoc) { return_flag = was_arrow; break; } + case tok_yield: { + next_cur(); + res = get_yield(); + kphp_error(false, "yield isn't supported"); + break; + } case tok_static: next_cur(); res = get_lambda_function(phpdoc, FunctionModifiers::static_lambda()); @@ -689,6 +706,14 @@ VertexPtr GenTree::get_expr_top(bool was_arrow, const PhpDocComment *phpdoc) { res = VertexAdaptor::create(get_expr_top(false)).set_location(auto_location()); break; } + case tok_throw: { + auto location = auto_location(); + next_cur(); + auto throw_expr = get_expression(); + CE (!kphp_error(throw_expr, "Empty expression in throw")); + res = VertexAdaptor::create(throw_expr).set_location(location); + break; + } default: return {}; } @@ -1009,7 +1034,8 @@ VertexAdaptor GenTree::get_return() { auto location = auto_location(); next_cur(); skip_phpdoc_tokens(); - VertexPtr return_val = get_expression(); + VertexPtr return_val; + return_val = get_expression(); if (!return_val && cur_function->is_main_function()) { return_val = VertexAdaptor::create(); } @@ -1024,10 +1050,33 @@ VertexAdaptor GenTree::get_return() { } ret = VertexAdaptor::create(return_val); } + CE (expect(tok_semicolon, "';'")); return ret.set_location(location); } +VertexAdaptor GenTree::get_yield() { + auto location = auto_location(); + next_cur(); + + bool is_yield_from = test_expect(tok_from); // processing the construction "yield from ..." + if (is_yield_from) { + next_cur(); + } + + VertexPtr yield_val = get_expression(); + if (!yield_val) { + yield_val = VertexAdaptor::create(); + } + + VertexAdaptor yield; + if (is_yield_from) { + yield = VertexAdaptor::create(yield_val); + } else { + yield = VertexAdaptor::create(yield_val); + } + return yield.set_location(location); +} template VertexAdaptor GenTree::get_break_or_continue() { @@ -1669,15 +1718,21 @@ void GenTree::parse_extends_implements() { VertexPtr GenTree::get_class(const PhpDocComment *phpdoc, ClassType class_type) { ClassModifiers modifiers; - if (test_expect(tok_abstract)) { - modifiers.set_abstract(); - } else if (test_expect(tok_final)) { - modifiers.set_final(); - } - if (modifiers.is_abstract() || modifiers.is_final()) { + while (vk::any_of_equal(cur->type(), tok_final, tok_abstract, tok_readonly)) { + if (test_expect(tok_abstract)) { + modifiers.set_abstract(); + } else if (test_expect(tok_final)) { + modifiers.set_final(); + } else if (test_expect(tok_readonly)) { + cur_class->is_immutable = true; + kphp_error(0, "`readonly` classes is not supported"); + } next_cur(); - CE(!kphp_error(cur->type() == tok_class, "`class` epxtected after abstract/final keyword")); + } + + if (modifiers.is_abstract() || modifiers.is_final() || cur_class->is_immutable) { + CE(!kphp_error(cur->type() == tok_class, "`class` epxtected after abstract/final/readonly keyword")); } CE(vk::any_of_equal(cur->type(), tok_class, tok_interface, tok_trait)); @@ -1720,7 +1775,7 @@ VertexPtr GenTree::get_class(const PhpDocComment *phpdoc, ClassType class_type) cur_class->file_id = processing_file; cur_class->set_name_and_src_name(full_class_name); // with full namespaces and slashes cur_class->phpdoc = phpdoc; - cur_class->is_immutable = phpdoc && phpdoc->has_tag(PhpDocType::kphp_immutable_class); + cur_class->is_immutable = cur_class->is_immutable || (phpdoc && phpdoc->has_tag(PhpDocType::kphp_immutable_class)); cur_class->location_line_num = line_num; bool registered = G->register_class(cur_class); @@ -1991,10 +2046,19 @@ GenericsInstantiationPhpComment *GenTree::parse_php_commentTs(vk::string_view st } VertexAdaptor GenTree::get_catch() { + auto location = auto_location(); + CE (expect(tok_catch, "'catch'")); CE (expect(tok_oppar, "'('")); auto exception_class = cur->str_val; CE (expect(tok_func_name, "type that implements Throwable")); + bool is_multiple_exception_types = false; + while (cur->type() == tok_or) { // processing catching multiple exception types (ExceptionType1 | ExceptionType2 | ...) + expect(tok_or, "union types"); + CE (expect(tok_func_name, "type that implements Throwable")); + is_multiple_exception_types = true; + } + kphp_error(!is_multiple_exception_types, "Catching multiple exception types isn't supported"); auto exception_var_name = get_expression(); CE (!kphp_error(exception_var_name, "Cannot parse catch")); CE (!kphp_error(exception_var_name->type() == op_var, "Expected variable name in 'catch'")); @@ -2003,12 +2067,24 @@ VertexAdaptor GenTree::get_catch() { auto catch_body = get_statement(); CE (!kphp_error(catch_body, "Cannot parse catch block")); - auto catch_op = VertexAdaptor::create(exception_var_name.as(), VertexUtil::embrace(catch_body)); + auto catch_op = VertexAdaptor::create(exception_var_name.as(), VertexUtil::embrace(catch_body)).set_location(location); catch_op->type_declaration = resolve_uses(cur_function, static_cast(exception_class)); return catch_op; } +VertexAdaptor GenTree::get_finally() { + auto location = auto_location(); + + CE (expect(tok_finally, "'finally'")); + auto finally_body = get_statement(); + CE (!kphp_error(finally_body, "Cannot parse finally block")); + + auto finally_op = VertexAdaptor::create(finally_body.as()).set_location(location); + + return finally_op; +} + VertexPtr GenTree::get_statement(const PhpDocComment *phpdoc) { TokenType type = cur->type(); @@ -2024,6 +2100,11 @@ VertexPtr GenTree::get_statement(const PhpDocComment *phpdoc) { } case tok_return: return get_return(); + case tok_yield: { + auto res = get_yield(); + CE(expect(tok_semicolon, "';'")); + return res; + } case tok_continue: return get_break_or_continue(); case tok_break: @@ -2064,6 +2145,9 @@ VertexPtr GenTree::get_statement(const PhpDocComment *phpdoc) { case tok_public: case tok_private: if (std::next(cur, 1)->type() == tok_const) { + if (cur_class->class_type == ClassType::trait) { + kphp_error(0, "`const` member is not supported in trait"); + } next_cur(); auto access = AccessModifiers::public_; @@ -2075,11 +2159,12 @@ VertexPtr GenTree::get_statement(const PhpDocComment *phpdoc) { return get_const_after_explicit_access_modifier(access); } // fall through + case tok_readonly: case tok_final: case tok_abstract: if (cur_function->type == FunctionData::func_class_holder) { return get_class_member(phpdoc); - } else if (vk::any_of_equal(cur->type(), tok_final, tok_abstract)) { + } else if (vk::any_of_equal(cur->type(), tok_final, tok_abstract, tok_readonly)) { return get_class(phpdoc, ClassType::klass); } next_cur(); @@ -2179,12 +2264,17 @@ VertexPtr GenTree::get_statement(const PhpDocComment *phpdoc) { while (test_expect(tok_catch)) { auto catch_op = get_catch(); CE (!kphp_error(catch_op, "Cannot parse catch statement")); - catch_op.set_location(location); catch_list.emplace_back(catch_op); } CE (!kphp_error(!catch_list.empty(), "Expected at least 1 'catch' statement")); - return VertexAdaptor::create(VertexUtil::embrace(try_body), std::move(catch_list)).set_location(location); + VertexPtr finally_op = VertexAdaptor::create(); + if (test_expect(tok_finally)) { + finally_op = get_finally(); + CE(!kphp_error(finally_op, "Cannot parse finally statement")); + } + + return VertexAdaptor::create(VertexUtil::embrace(try_body), std::move(catch_list), finally_op).set_location(location); } case tok_inline_html: { auto html_code = VertexAdaptor::create().set_location(auto_location()); diff --git a/compiler/gentree.h b/compiler/gentree.h index 31e5fe6314..3dc66f8dce 100644 --- a/compiler/gentree.h +++ b/compiler/gentree.h @@ -66,6 +66,7 @@ class GenTree { VertexPtr get_expression(); VertexPtr get_statement(const PhpDocComment *phpdoc = nullptr); VertexAdaptor get_catch(); + VertexAdaptor get_finally(); void get_instance_var_list(const PhpDocComment *phpdoc, FieldModifiers modifiers, const TypeHint *type_hint); void get_traits_uses(); void get_use(); @@ -92,6 +93,7 @@ class GenTree { template::ResultType> VertexAdaptor get_multi_call(FuncT &&f, bool parenthesis = false); VertexAdaptor get_return(); + VertexAdaptor get_yield(); template VertexAdaptor get_break_or_continue(); VertexAdaptor get_foreach(); @@ -152,4 +154,3 @@ class GenTree { FunctionPtr cur_function; // = functions_stack.back() SrcFilePtr processing_file; }; - diff --git a/compiler/inferring/expr-node.cpp b/compiler/inferring/expr-node.cpp index 532f36ad8f..b280666783 100644 --- a/compiler/inferring/expr-node.cpp +++ b/compiler/inferring/expr-node.cpp @@ -88,8 +88,13 @@ void ExprNodeRecalc::recalc_c2php(VertexAdaptor conv) { } void ExprNodeRecalc::recalc_ternary(VertexAdaptor ternary) { - set_lca(ternary->true_expr()); - set_lca(ternary->false_expr()); + auto true_expr = ternary->true_expr(); + auto false_expr = ternary->false_expr(); + if (true_expr->type() == op_throw || false_expr->type() == op_throw) { // while throw expression isn't implemented + kphp_error(false, TermStringFormat::paint("throw expression ", TermStringFormat::blue) + "isn't supported"); + } + set_lca(true_expr); + set_lca(false_expr); } void ExprNodeRecalc::recalc_func_call(VertexAdaptor call) { @@ -492,6 +497,10 @@ void ExprNodeRecalc::recalc_expr(VertexPtr expr) { recalc_ptype(); break; + case op_throw: + recalc_ptype(); + break; + default: break; } diff --git a/compiler/inferring/primitive-type.cpp b/compiler/inferring/primitive-type.cpp index f88e82768a..1505071d0f 100644 --- a/compiler/inferring/primitive-type.cpp +++ b/compiler/inferring/primitive-type.cpp @@ -20,6 +20,7 @@ const char *ptype_name(PrimitiveType id) { case tp_int: return "int"; case tp_float: return "float"; case tp_array: return "array"; + case tp_iterable: return "iterable"; case tp_string: return "string"; case tp_tmp_string: return "tmp_string"; case tp_mixed: return "mixed"; diff --git a/compiler/inferring/primitive-type.h b/compiler/inferring/primitive-type.h index f6fec41c0c..b4b889c3b2 100644 --- a/compiler/inferring/primitive-type.h +++ b/compiler/inferring/primitive-type.h @@ -16,6 +16,7 @@ enum PrimitiveType { tp_string, tp_tmp_string, tp_array, + tp_iterable, tp_mixed, tp_void, tp_tuple, diff --git a/compiler/inferring/type-data.cpp b/compiler/inferring/type-data.cpp index 41efacd742..2e8ab32f51 100644 --- a/compiler/inferring/type-data.cpp +++ b/compiler/inferring/type-data.cpp @@ -185,6 +185,9 @@ PrimitiveType TypeData::get_real_ptype() const { if (p == tp_any && (or_null_flag() || or_false_flag())) { return tp_bool; } + if (p == tp_iterable) { + kphp_error(false, "Iterable type isn't supported"); + } return p; } @@ -781,6 +784,7 @@ int type_strlen(const TypeData *type) { return STRLEN_INT; case tp_float: return STRLEN_FLOAT; + case tp_iterable: // STRLEN_ARRAY_(array), because STRLEN_CLASS(Traversable) not implemented (will be subject to change) case tp_array: case tp_tuple: case tp_shape: diff --git a/compiler/keywords.gperf b/compiler/keywords.gperf index e1260987d0..fbc79613ce 100644 --- a/compiler/keywords.gperf +++ b/compiler/keywords.gperf @@ -25,6 +25,7 @@ array, tok_array tuple, tok_tuple shape, tok_shape Array, tok_array +iterable, tok_iterable as, tok_as case, tok_case switch, tok_switch @@ -39,8 +40,11 @@ global, tok_global static, tok_static final, tok_final abstract, tok_abstract +readonly, tok_readonly goto, tok_goto return, tok_return +yield, tok_yield +from, tok_from list, tok_list include, tok_require include_once, tok_require_once @@ -91,6 +95,7 @@ new, tok_new throw, tok_throw try, tok_try catch, tok_catch +finally, tok_finally namespace, tok_namespace public, tok_public private, tok_private diff --git a/compiler/lexer.cpp b/compiler/lexer.cpp index aee442b81a..fc297ad07b 100644 --- a/compiler/lexer.cpp +++ b/compiler/lexer.cpp @@ -170,6 +170,7 @@ void LexerData::hack_last_tokens() { {tok_object, tok_conv_object}, {tok_bool, tok_conv_bool}, {tok_boolean, tok_conv_bool}, + {tok_iterable, tok_conv_iterable}, }; for (auto &cast : casts) { // check the middle token, the one that goes before ')' @@ -417,6 +418,7 @@ bool TokenLexerNum::parse(LexerData *lexer_data) const { finish, hex, binary, + octal, } state = before_dot; if (s[0] == '0' && s[1] == 'x') { @@ -425,6 +427,12 @@ bool TokenLexerNum::parse(LexerData *lexer_data) const { } else if (s[0] == '0' && s[1] == 'b') { t += 2; state = binary; + } else if (s[0] == '0' && (s[1] == 'o' || s[1] == 'O' || is_digit(s[1]))) { // 0123 or 0o123 or 0O123 + t++; + if (s[1] == 'o' || s[1] == 'O') { + t++; + } + state = octal; } bool with_separator = false; @@ -462,6 +470,17 @@ bool TokenLexerNum::parse(LexerData *lexer_data) const { } break; } + case octal: { + switch(*t) { + case '0' ... '7': + t++; + break; + default: + state = finish; + break; + } + break; + } case before_dot: { switch (*t) { case '0' ... '9': { @@ -559,16 +578,6 @@ bool TokenLexerNum::parse(LexerData *lexer_data) const { } } - if (!is_float) { - if (s[0] == '0' && s[1] != 'x' && s[1] != 'b') { - for (int i = 0; i < t - s; i++) { - if (s[i] < '0' || s[i] > '7') { - return TokenLexerError("Bad octal number").parse(lexer_data); - } - } - } - } - assert (t != s); auto token_type = is_float ? tok_float_const : tok_int_const; diff --git a/compiler/phpdoc.cpp b/compiler/phpdoc.cpp index 7fb34263bc..a98bdd2771 100644 --- a/compiler/phpdoc.cpp +++ b/compiler/phpdoc.cpp @@ -378,6 +378,7 @@ const TypeHint *PhpDocTypeHintParser::parse_simple_type() { cur_tok++; return TypeHintPrimitive::create(tp_False); case tok_true: + kphp_error(0, "Standalone true type is not supported"); cur_tok++; return TypeHintPrimitive::create(tp_bool); case tok_null: @@ -412,6 +413,9 @@ const TypeHint *PhpDocTypeHintParser::parse_simple_type() { return TypeHintArray::create(type_hints.back()); } return TypeHintArray::create_array_of_any(); + case tok_iterable: + cur_tok++; + return TypeHintPrimitive::create(tp_iterable); case tok_at: { // @tl\... cur_tok++; if (!cur_tok->str_val.starts_with("tl\\")) { @@ -684,7 +688,13 @@ const TypeHint *PhpDocTypeHintParser::parse_shape_type() { const TypeHint *PhpDocTypeHintParser::parse_type_expression() { const TypeHint *result = parse_type_array(); - if (cur_tok->type() != tok_or) { + + auto is_intersection_type_cur_tok = [this]() { + return cur_tok->type() == tok_and + && (cur_tok + 1)->type() != tok_var_name; + }; // T& $var_name -> false, but T1&T2 $var_name -> true + + if (cur_tok->type() != tok_or && !is_intersection_type_cur_tok()) { return result; } @@ -703,7 +713,10 @@ const TypeHint *PhpDocTypeHintParser::parse_type_expression() { }; on_each_item(result); - while (cur_tok->type() == tok_or) { + while (cur_tok->type() == tok_or || is_intersection_type_cur_tok()) { + if (cur_tok->type() == tok_and) { + kphp_error(0, "Intersection types is not supported"); + } cur_tok++; on_each_item(parse_type_array()); diff --git a/compiler/pipes/cfg.cpp b/compiler/pipes/cfg.cpp index d560979389..6a167504d4 100644 --- a/compiler/pipes/cfg.cpp +++ b/compiler/pipes/cfg.cpp @@ -656,6 +656,10 @@ void CFG::create_cfg(VertexPtr tree_node, Node *res_start, Node *res_finish, boo *res_finish = Node(); break; } + case op_yield: + case op_yield_from: + kphp_error(false, "yield isn't supported"); + break; case op_set: { auto set_op = tree_node.as(); Node a, b; diff --git a/compiler/pipes/convert-list-assignments.cpp b/compiler/pipes/convert-list-assignments.cpp index 423c62cf32..82e4f7ce67 100644 --- a/compiler/pipes/convert-list-assignments.cpp +++ b/compiler/pipes/convert-list-assignments.cpp @@ -19,6 +19,9 @@ VertexPtr ConvertListAssignmentsPass::process_list_assignment(VertexAdaptortype() == op_index) { expr = expr.as()->array(); } + if (expr->type() == op_addr) { + kphp_error(false, "Links in list() isn't supported"); + } auto var_in_list = expr.try_as(); if (var_in_list) { if (var_in_list->str_val == list->array().as()->str_val) { diff --git a/compiler/pipes/fix-returns.cpp b/compiler/pipes/fix-returns.cpp index 35fcea213a..313801697a 100644 --- a/compiler/pipes/fix-returns.cpp +++ b/compiler/pipes/fix-returns.cpp @@ -20,6 +20,9 @@ VertexPtr FixReturnsPass::on_enter_vertex(VertexPtr root) { FunctionPtr fun = call->func_id; kphp_error(0, fmt_format("Using result of void function {}", fun->as_human_readable())); } else { + if (root->type() == op_throw) { + kphp_error(false, TermStringFormat::paint("throw expression ", TermStringFormat::blue) + "isn't supported"); + } kphp_error(0, "Using result of void expression"); } } diff --git a/compiler/pipes/gen-tree-postprocess.cpp b/compiler/pipes/gen-tree-postprocess.cpp index ddd9b2a94b..e880741d3a 100644 --- a/compiler/pipes/gen-tree-postprocess.cpp +++ b/compiler/pipes/gen-tree-postprocess.cpp @@ -226,6 +226,14 @@ VertexPtr GenTreePostprocessPass::on_enter_vertex(VertexPtr root) { } } + // replace octal number representation (0o123 or 0O123 (php) -> 0123 (c++)) + if (auto int_const_vertex = root.try_as()) { + std::string str = int_const_vertex->str_val; + if (str.find('o') == 1 || str.find('O') == 1) { + int_const_vertex->str_val.erase(1, 1); + } + } + return root; } diff --git a/compiler/token.h b/compiler/token.h index 8303527035..116087aca5 100644 --- a/compiler/token.h +++ b/compiler/token.h @@ -41,6 +41,7 @@ enum TokenType { tok_array, tok_tuple, tok_shape, + tok_iterable, tok_as, tok_case, tok_switch, @@ -56,6 +57,8 @@ enum TokenType { tok_do, tok_eval, tok_return, + tok_yield, + tok_from, tok_list, tok_include, tok_include_once, @@ -68,6 +71,7 @@ enum TokenType { tok_static, tok_final, tok_abstract, + tok_readonly, tok_goto, tok_isset, tok_declare, @@ -165,6 +169,7 @@ enum TokenType { tok_conv_array, tok_conv_object, tok_conv_bool, + tok_conv_iterable, tok_false, tok_true, @@ -179,6 +184,7 @@ enum TokenType { tok_try, tok_catch, + tok_finally, tok_public, tok_private, diff --git a/compiler/vertex-desc.json b/compiler/vertex-desc.json index 493011e3c0..2b41b93707 100644 --- a/compiler/vertex-desc.json +++ b/compiler/vertex-desc.json @@ -588,6 +588,11 @@ "array": 0 } }, + { + "comment": "$f(...), when used first-class callable", + "name": "op_ellipsis", + "base_name": "meta_op_base" + }, { "comment": "var(); or var() = default_value(); type hints and @param are stored in type_hint", "name": "op_func_param", @@ -934,6 +939,29 @@ "str": "return" } }, + { + "comment": "yield expr()", + "name": "op_yield", + "base_name": "meta_op_base", + "sons": { + "expr": { + "id": 0, + "optional": true + } + + }, + "props": { + "rl": "rl_op", + "cnst": "cnst_not_func", + "type": "common_op", + "str": "yield" + } + }, + { + "comment": "yield from expr()", + "name": "op_yield_from", + "base_name": "op_yield" + }, { "comment": "lsh() == rhs()", "name": "op_eq2", @@ -1874,19 +1902,43 @@ "str": "catch" } }, + + { + "comment": "finally { cmd() }", + "name": "op_finally", + "base_name": "meta_op_base", + "sons": { + "cmd": { + "id": 0, + "type": "op_seq" + } + }, + "props": { + "rl": "rl_common", + "cnst": "cnst_not_func", + "type": "common_op", + "str": "finally" + } + }, + { "comment": "try { try_cmd()... } catch_list()...; catches_all is true if all exceptions are handled by this try statement", "sons": { "try_cmd": { "id": 0, "type": "op_seq" + }, + "finally_cmd": { + "id": -1, + "type": "op_finally" } }, "ranges": { "catch_list": [ 1, - 0 + -1 ] + }, "name": "op_try", "base_name": "meta_op_base", @@ -1948,7 +2000,7 @@ "exception": 0 }, "props": { - "rl": "rl_common", + "rl": "rl_op", "cnst": "cnst_not_func", "type": "common_op", "str": "throw" diff --git a/tests/cpp/compiler/lexer-test.cpp b/tests/cpp/compiler/lexer-test.cpp index ef2cd8e8b9..21d7d45a21 100644 --- a/tests/cpp/compiler/lexer-test.cpp +++ b/tests/cpp/compiler/lexer-test.cpp @@ -35,10 +35,31 @@ TEST(lexer_test, test_php_tokens) { {";", {"tok_semicolon(;)"}}, + {"} finally {", {"tok_clbrc(})", "tok_finally(finally)", "tok_opbrc({)"}}, + {"function finally()", {"tok_function(function)", "tok_finally(finally)", "tok_oppar(()", "tok_clpar())"}}, + {"public function finally()", {"tok_public(public)", "tok_function(function)", "tok_finally(finally)", "tok_oppar(()", "tok_clpar())"}}, + {"private static function finally", {"tok_private(private)", "tok_static(static)", "tok_function(function)", "tok_finally(finally)"}}, + {"const finally", {"tok_const(const)", "tok_finally(finally)"}}, + {"new Exception()", {"tok_new(new)", "tok_func_name(Exception)", "tok_oppar(()", "tok_clpar())"}}, {"new \\Exception()", {"tok_new(new)", "tok_func_name(\\Exception)", "tok_oppar(()", "tok_clpar())"}}, {"new Exception('test')", {"tok_new(new)", "tok_func_name(Exception)", "tok_oppar(()", "tok_str(test)", "tok_clpar())"}}, + {"readonly class A {", {"tok_readonly(readonly)", "tok_class(class)", "tok_func_name(A)", "tok_opbrc({)"}}, + {"readonly final class A {", {"tok_readonly(readonly)", "tok_final(final)", "tok_class(class)", "tok_func_name(A)", "tok_opbrc({)"}}, + {"final readonly class A {", {"tok_final(final)", "tok_readonly(readonly)", "tok_class(class)", "tok_func_name(A)", "tok_opbrc({)"}}, + {"readonly abstract class A {", {"tok_readonly(readonly)", "tok_abstract(abstract)", "tok_class(class)", "tok_func_name(A)", "tok_opbrc({)"}}, + {"abstract readonly class A {", {"tok_abstract(abstract)", "tok_readonly(readonly)", "tok_class(class)", "tok_func_name(A)", "tok_opbrc({)"}}, + + // first class callable syntax + {"f(...)", {"tok_func_name(f)", "tok_oppar(()", "tok_varg(...)", "tok_clpar())"}}, + {"'f'(...)", {"tok_str(f)", "tok_oppar(()", "tok_varg(...)", "tok_clpar())"}}, + {"$obj->method(...)", {"tok_var_name($obj)", "tok_arrow(->)", "tok_func_name(method)", "tok_oppar(()", "tok_varg(...)", "tok_clpar())"}}, + {"Foo::method(...)", {"tok_func_name(Foo::method)", "tok_oppar(()", "tok_varg(...)", "tok_clpar())"}}, + {"$f(...)", {"tok_var_name($f)", "tok_oppar(()", "tok_varg(...)", "tok_clpar())"}}, + // splat operator + {"f(...$args)", {"tok_func_name(f)", "tok_oppar(()", "tok_varg(...)", "tok_var_name($args)", "tok_clpar())"}}, + {"'abc'", {"tok_str(abc)"}}, {"12 + 4", {"tok_int_const(12)", "tok_plus(+)", "tok_int_const(4)"}}, {"12_100 + 4_5.56", {"tok_int_const_sep(12_100)", "tok_plus(+)", "tok_float_const_sep(4_5.56)"}}, @@ -125,7 +146,6 @@ 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(;)"}}, }; - for (const auto &test : tests) { std::string input = "f_old(...); +echo $f_new(); diff --git a/tests/phpt/cl/208_first_class_callable_syntax.php b/tests/phpt/cl/208_first_class_callable_syntax.php new file mode 100644 index 0000000000..fbc27d60a8 --- /dev/null +++ b/tests/phpt/cl/208_first_class_callable_syntax.php @@ -0,0 +1,10 @@ +@kphp_should_fail +\First class callable syntax is not supported\ +t = $t; + } + + public function getVariable(): true { + return $this->t; + } +} + +/** @var true */ +$t = true; +$obj = new A($t); +var_dump($obj->getVariable()); diff --git a/tests/phpt/cl/214_fail_iterable_pseudo-type_narrowing.php b/tests/phpt/cl/214_fail_iterable_pseudo-type_narrowing.php new file mode 100644 index 0000000000..d28875edd1 --- /dev/null +++ b/tests/phpt/cl/214_fail_iterable_pseudo-type_narrowing.php @@ -0,0 +1,17 @@ +@kphp_should_fail +\Iterable type isn't supported\ +method($arr); diff --git a/tests/phpt/constants/Traits/01_const_in_trait.php b/tests/phpt/constants/Traits/01_const_in_trait.php new file mode 100644 index 0000000000..98452d6456 --- /dev/null +++ b/tests/phpt/constants/Traits/01_const_in_trait.php @@ -0,0 +1,19 @@ +@kphp_should_fail +\"const" member is not supported in trait\ +t_ = 1; + echo $this->t_; + } + } + + class B { + use T; + } + + $obj = new B(); + $obj->f(); diff --git a/tests/phpt/exceptions/catching_multiple_exception_types/01_try_multicatch.php b/tests/phpt/exceptions/catching_multiple_exception_types/01_try_multicatch.php new file mode 100644 index 0000000000..b166bfeb30 --- /dev/null +++ b/tests/phpt/exceptions/catching_multiple_exception_types/01_try_multicatch.php @@ -0,0 +1,12 @@ +@kphp_should_fail +\Catching multiple exception types isn't supported\ +getMessage(); +} diff --git a/tests/phpt/exceptions/throw_expression/06_ok_throw_expr.php b/tests/phpt/exceptions/throw_expression/06_ok_throw_expr.php new file mode 100644 index 0000000000..963e23523c --- /dev/null +++ b/tests/phpt/exceptions/throw_expression/06_ok_throw_expr.php @@ -0,0 +1,8 @@ +@ok + +try { + $callable = fn() => throw new Exception("err"); + $callable(); +} catch (Exception $err) { + echo $err->getMessage(); +} diff --git a/tests/phpt/exceptions/with_finally/01_finally_empty.php b/tests/phpt/exceptions/with_finally/01_finally_empty.php new file mode 100644 index 0000000000..2cb5f6180c --- /dev/null +++ b/tests/phpt/exceptions/with_finally/01_finally_empty.php @@ -0,0 +1,11 @@ +@kphp_should_fail +/`finally` construct is not implemented/ + $input; +} + +foreach(foo(["value"]) as $id => $f) { + // ... +} diff --git a/tests/phpt/yield/03_fail_yield.php b/tests/phpt/yield/03_fail_yield.php new file mode 100644 index 0000000000..e1d0972adb --- /dev/null +++ b/tests/phpt/yield/03_fail_yield.php @@ -0,0 +1,10 @@ +@kphp_should_fail +/yield isn't supported/ +