From 3485984958fd82480d990e47a81ede1ee67170a3 Mon Sep 17 00:00:00 2001 From: Stanislav Eismont Date: Mon, 28 Jun 2021 14:37:11 +0300 Subject: [PATCH] Add support for anonymous classes --- compiler/gentree.cpp | 73 ++++++++++++------- compiler/gentree.h | 5 +- compiler/lexer.cpp | 3 +- .../phpt/anon_classes/001_set_to_variable.php | 13 ++++ .../anon_classes/002_pass_to_function.php | 20 +++++ .../003_constructor_arguments.php | 21 ++++++ .../004_access_to_outer_class.php | 32 ++++++++ tests/phpt/anon_classes/005_extend_traits.php | 24 ++++++ .../006_anon_classes_equality.php | 17 +++++ .../007_constructor_type_mismatch.php | 11 +++ 10 files changed, 192 insertions(+), 27 deletions(-) create mode 100644 tests/phpt/anon_classes/001_set_to_variable.php create mode 100644 tests/phpt/anon_classes/002_pass_to_function.php create mode 100644 tests/phpt/anon_classes/003_constructor_arguments.php create mode 100644 tests/phpt/anon_classes/004_access_to_outer_class.php create mode 100644 tests/phpt/anon_classes/005_extend_traits.php create mode 100644 tests/phpt/anon_classes/006_anon_classes_equality.php create mode 100644 tests/phpt/anon_classes/007_constructor_type_mismatch.php diff --git a/compiler/gentree.cpp b/compiler/gentree.cpp index 37c15588b1..d2f66a4206 100644 --- a/compiler/gentree.cpp +++ b/compiler/gentree.cpp @@ -223,10 +223,8 @@ VertexAdaptor GenTree::get_require(bool once) { } template -VertexAdaptor GenTree::get_func_call() { +VertexAdaptor GenTree::get_func_call(std::string name) { auto location = auto_location(); - string name{cur->str_val}; - next_cur(); CE (expect(tok_oppar, "'('")); skip_phpdoc_tokens(); @@ -243,6 +241,28 @@ VertexAdaptor GenTree::get_func_call() { return call; } +template +VertexAdaptor GenTree::get_func_call() { + string name{cur->str_val}; + next_cur(); + return get_func_call(name); +} + +VertexAdaptor GenTree::get_type_and_args_of_new_expression() { + if (test_expect(tok_class)) { + // here we met anon class definition and probably constructor's arguments + return get_class({}, ClassType::klass, true); + } + + CE(!kphp_error(cur->type() == tok_func_name, "Expected class name after new")); + auto func_call = get_func_call(); + // hack to be more compatible with php + if (func_call->str_val == "Memcache") { + func_call->set_string("McMemcache"); + } + return func_call; +} + VertexAdaptor GenTree::get_short_array() { auto location = auto_location(); next_cur(); @@ -526,13 +546,7 @@ VertexPtr GenTree::get_expr_top(bool was_arrow) { case tok_new: { next_cur(); - CE(!kphp_error(cur->type() == tok_func_name, "Expected class name after new")); - auto func_call = get_func_call(); - // Hack to be more compatible with php - if (func_call->str_val == "Memcache") { - func_call->set_string("McMemcache"); - } - + auto func_call = get_type_and_args_of_new_expression(); res = gen_constructor_call_with_args(func_call->str_val, func_call->get_next()).set_location(func_call); CE(res); break; @@ -1617,7 +1631,7 @@ void GenTree::parse_extends_implements() { } } -VertexPtr GenTree::get_class(vk::string_view phpdoc_str, ClassType class_type) { +VertexAdaptor GenTree::get_class(vk::string_view phpdoc_str, ClassType class_type, bool is_anonymous) { ClassModifiers modifiers; if (test_expect(tok_abstract)) { modifiers.set_abstract(); @@ -1633,20 +1647,26 @@ VertexPtr GenTree::get_class(vk::string_view phpdoc_str, ClassType class_type) { CE(vk::any_of_equal(cur->type(), tok_class, tok_interface, tok_trait)); next_cur(); - CE (!kphp_error(test_expect(tok_func_name), "Class name expected")); - - auto name_str = cur->str_val; - next_cur(); + VertexAdaptor res; + std::string class_name; + if (is_anonymous) { + class_name = gen_unique_name("anon_class", cur_function); + res = get_func_call(class_name); + } else { + CE (!kphp_error(test_expect(tok_func_name), "Class name expected")); + class_name = std::string{cur->str_val.data(), cur->str_val.size()}; + next_cur(); + } std::string full_class_name; if (processing_file->namespace_name.empty() && phpdoc_tag_exists(phpdoc_str, php_doc_tag::kphp_tl_class)) { kphp_assert(processing_file->is_builtin()); - full_class_name = G->settings().tl_namespace_prefix.get() + name_str; + full_class_name = G->settings().tl_namespace_prefix.get() + class_name; } else { - full_class_name = processing_file->namespace_name.empty() ? std::string{name_str} : processing_file->namespace_name + "\\" + name_str; + full_class_name = processing_file->namespace_name.empty() ? class_name : processing_file->namespace_name + "\\" + class_name; } - kphp_error(processing_file->namespace_uses.find(name_str) == processing_file->namespace_uses.end(), + kphp_error(processing_file->namespace_uses.find(class_name) == processing_file->namespace_uses.end(), "Class name is the same as one of 'use' at the top of the file"); StackPushPop c_alive(class_stack, cur_class, ClassPtr(new ClassData())); @@ -1660,7 +1680,7 @@ VertexPtr GenTree::get_class(vk::string_view phpdoc_str, ClassType class_type) { parse_extends_implements(); auto name_vertex = VertexAdaptor::create().set_location(auto_location()); - name_vertex->str_val = static_cast(name_str); + name_vertex->str_val = class_name; cur_class->file_id = processing_file; cur_class->set_name_and_src_name(full_class_name, phpdoc_str); // with full namespaces and slashes @@ -1686,7 +1706,7 @@ VertexPtr GenTree::get_class(vk::string_view phpdoc_str, ClassType class_type) { G->register_and_require_function(cur_function, parsed_os, true); // push the class down the pipeline } - return {}; + return res; } @@ -2023,7 +2043,8 @@ VertexPtr GenTree::get_statement(vk::string_view phpdoc_str) { if (cur_function->type == FunctionData::func_class_holder) { return get_class_member(phpdoc_str); } else if (vk::any_of_equal(cur->type(), tok_final, tok_abstract)) { - return get_class(phpdoc_str, ClassType::klass); + get_class(phpdoc_str, ClassType::klass); + return {}; } next_cur(); kphp_error(0, "Unexpected access modifier outside of class body"); @@ -2153,13 +2174,15 @@ VertexPtr GenTree::get_statement(vk::string_view phpdoc_str) { return empty; } case tok_class: - return get_class(phpdoc_str, ClassType::klass); + get_class(phpdoc_str, ClassType::klass); + return {}; case tok_interface: - return get_class(phpdoc_str, ClassType::interface); + get_class(phpdoc_str, ClassType::interface); + return {}; case tok_trait: { - auto res = get_class(phpdoc_str, ClassType::trait); + get_class(phpdoc_str, ClassType::trait); kphp_error(processing_file->namespace_uses.empty(), "Usage of operator `use`(Aliasing/Importing) with traits is temporarily prohibited"); - return res; + return {}; } default: { auto res = get_expression(); diff --git a/compiler/gentree.h b/compiler/gentree.h index e551d9b911..8dd49c0d69 100644 --- a/compiler/gentree.h +++ b/compiler/gentree.h @@ -103,7 +103,10 @@ class GenTree { VertexAdaptor get_conv(); VertexAdaptor get_require(bool once); template + VertexAdaptor get_func_call(std::string name); + template VertexAdaptor get_func_call(); + VertexAdaptor get_type_and_args_of_new_expression(); VertexAdaptor get_short_array(); VertexAdaptor get_string(); VertexAdaptor get_string_build(); @@ -143,7 +146,7 @@ class GenTree { static VertexAdaptor gen_constructor_call_with_args(std::string allocated_class_name, std::vector args); static VertexAdaptor gen_constructor_call_with_args(ClassPtr allocated_class, std::vector args); - VertexPtr get_class(vk::string_view phpdoc_str, ClassType class_type); + VertexAdaptor get_class(vk::string_view phpdoc_str, ClassType class_type, bool is_anonymous = false); void parse_extends_implements(); static VertexPtr process_arrow(VertexPtr lhs, VertexPtr rhs); diff --git a/compiler/lexer.cpp b/compiler/lexer.cpp index 3eb1daba91..325d7ede2d 100644 --- a/compiler/lexer.cpp +++ b/compiler/lexer.cpp @@ -211,7 +211,8 @@ void LexerData::hack_last_tokens() { return; } - if (are_last_tokens(tok_new, tok_func_name, except_token_tag{})) { + if (are_last_tokens(tok_new, tok_func_name, except_token_tag{}) || + are_last_tokens(tok_new, tok_class, except_token_tag{})) { Token t = tokens.back(); tokens.pop_back(); tokens.emplace_back(tok_oppar); diff --git a/tests/phpt/anon_classes/001_set_to_variable.php b/tests/phpt/anon_classes/001_set_to_variable.php new file mode 100644 index 0000000000..561d61f35c --- /dev/null +++ b/tests/phpt/anon_classes/001_set_to_variable.php @@ -0,0 +1,13 @@ +@ok +say_foo(); +} + +test_set_to_variable(); diff --git a/tests/phpt/anon_classes/002_pass_to_function.php b/tests/phpt/anon_classes/002_pass_to_function.php new file mode 100644 index 0000000000..19eee0b21c --- /dev/null +++ b/tests/phpt/anon_classes/002_pass_to_function.php @@ -0,0 +1,20 @@ +@ok +say_foo(); +} + +function test_pass_to_function() { + call_say_foo(new class implements I { + function say_foo() { + echo "foo"; + } + }); +} + +test_pass_to_function(); diff --git a/tests/phpt/anon_classes/003_constructor_arguments.php b/tests/phpt/anon_classes/003_constructor_arguments.php new file mode 100644 index 0000000000..96eee4d21a --- /dev/null +++ b/tests/phpt/anon_classes/003_constructor_arguments.php @@ -0,0 +1,21 @@ +@ok +prop) extends Outer { + private $prop3; + + public function __construct($prop) { + $this->prop3 = $prop; + } + + public function func3() { + return $this->prop2 + $this->prop3 + $this->func1(); + } + }; + } +} + +function test_access_to_outer_class() { + echo (new Outer)->func2()->func3(); +} + +test_access_to_outer_class(); diff --git a/tests/phpt/anon_classes/005_extend_traits.php b/tests/phpt/anon_classes/005_extend_traits.php new file mode 100644 index 0000000000..97049342c6 --- /dev/null +++ b/tests/phpt/anon_classes/005_extend_traits.php @@ -0,0 +1,24 @@ +@ok +sayHello(); +} + +test_extend_traits(); diff --git a/tests/phpt/anon_classes/006_anon_classes_equality.php b/tests/phpt/anon_classes/006_anon_classes_equality.php new file mode 100644 index 0000000000..964ac29895 --- /dev/null +++ b/tests/phpt/anon_classes/006_anon_classes_equality.php @@ -0,0 +1,17 @@ +@ok +