diff --git a/compiler/debug.cpp b/compiler/debug.cpp index 0164a5f4d3..b3a784f54c 100644 --- a/compiler/debug.cpp +++ b/compiler/debug.cpp @@ -184,6 +184,7 @@ std::string debugTokenName(TokenType t) { {tok_phpdoc, "tok_phpdoc"}, {tok_clone, "tok_clone"}, {tok_instanceof, "tok_instanceof"}, + {tok_attribute_start, "tok_attribute_start"}, {tok_end, "tok_end"}, }); diff --git a/compiler/gentree.cpp b/compiler/gentree.cpp index aebdcd0ddc..707a8aa17d 100644 --- a/compiler/gentree.cpp +++ b/compiler/gentree.cpp @@ -875,6 +875,10 @@ VertexPtr GenTree::get_def_value() { VertexAdaptor GenTree::get_func_param() { auto location = auto_location(); + if (test_expect(tok_attribute_start)) { + get_attribute_group(); + } + const TypeHint *type_hint = get_typehint(); bool is_varg = false; @@ -2139,6 +2143,8 @@ VertexPtr GenTree::get_statement(vk::string_view phpdoc_str) { return get_foreach(); case tok_switch: return get_switch(); + case tok_attribute_start: + return get_attribute_group(); case tok_phpdoc: { // enter /** ... */ // is it a function/class/field phpdoc? auto next = (cur+1)->type(); @@ -2233,6 +2239,52 @@ VertexPtr GenTree::get_statement(vk::string_view phpdoc_str) { kphp_fail(); } +VertexAdaptor GenTree::get_attribute_group() { + get_attribute(); + while (test_expect(tok_attribute_start)) { + get_attribute(); + } + return VertexAdaptor::create(); +} + +VertexAdaptor GenTree::get_attribute() { + next_cur(); + auto calls = std::vector>(); + const auto ok = gen_list(&calls, &GenTree::get_attribute_call, tok_comma); + CE(!kphp_error(ok, "Failed to parse attribute call")); + CE(expect(tok_clbrk, "']'")); + + return VertexAdaptor::create(); +} + +VertexAdaptor GenTree::get_attribute_call() { + const auto location = auto_location(); + if (cur->type() == tok_clbrk) { + return {}; + } + + const auto name = std::string(cur->str_val); + next_cur(); + + VertexAdaptor call; + + if (test_expect(tok_oppar)) { + CE (expect(tok_oppar, "'('")); + skip_phpdoc_tokens(); + vector next; + bool ok_next = gen_list(&next, &GenTree::get_expression, tok_comma); + CE (!kphp_error(ok_next, "get argument list failed")); + CE (expect(tok_clpar, "')'")); + + call = VertexAdaptor::create_vararg(next).set_location(location); + } + + call = VertexAdaptor::create_vararg().set_location(location); + + call->set_string(name); + return call; +} + VertexPtr GenTree::get_const_after_explicit_access_modifier(AccessModifiers access) { CE(!kphp_error(cur_class, "Access modifier for const allowed in class only")); if (cur_class->is_interface()) { diff --git a/compiler/gentree.h b/compiler/gentree.h index 31fe78c097..9462a759c5 100644 --- a/compiler/gentree.h +++ b/compiler/gentree.h @@ -128,6 +128,10 @@ class GenTree { VertexAdaptor get_anonymous_function(TokenType tok = tok_function, bool is_static = false); VertexAdaptor get_function(TokenType tok, vk::string_view phpdoc_str, FunctionModifiers modifiers, std::vector> *uses_of_lambda = nullptr); + VertexAdaptor get_attribute_group(); + VertexAdaptor get_attribute(); + VertexAdaptor get_attribute_call(); + ClassMemberModifiers parse_class_member_modifier_mask(); VertexPtr get_class_member(vk::string_view phpdoc_str); diff --git a/compiler/lexer.cpp b/compiler/lexer.cpp index 01dae66c9d..0cad431322 100644 --- a/compiler/lexer.cpp +++ b/compiler/lexer.cpp @@ -902,6 +902,12 @@ bool TokenLexerComment::parse(LexerData *lexer_data) const { const char *s = lexer_data->get_code(), *st = s; + if (s[0] == '#' && s[1] == '[') { + lexer_data->add_token(0, tok_attribute_start, s, s+2); + lexer_data->pass(2); + return vk::singleton::get().parse(lexer_data); + } + assert (s[0] == '/' || s[0] == '#'); if (s[0] == '#' || s[1] == '/') { while (s[0] && s[0] != '\n') { diff --git a/compiler/phpdoc.cpp b/compiler/phpdoc.cpp index 5424bf6b71..61144ca2da 100644 --- a/compiler/phpdoc.cpp +++ b/compiler/phpdoc.cpp @@ -437,7 +437,7 @@ const TypeHint *PhpDocTypeRuleParser::parse_type_expression() { bool was_raw_bool = false; bool was_false = false; bool was_null = false; - + auto on_each_item = [&](const TypeHint *item) { items.emplace_back(item); if (const auto *as_primitive = item->try_as()) { @@ -457,7 +457,7 @@ const TypeHint *PhpDocTypeRuleParser::parse_type_expression() { } } - // try to simplify: T|false and similar as an optional, not as a vector + // try to simplify: T|false and similar to an optional, not as a vector bool can_be_simplified_as_optional = 1 == (items.size() - was_false - was_null); if (can_be_simplified_as_optional) { for (const TypeHint *item : items) { diff --git a/compiler/token.h b/compiler/token.h index fecd6473d6..179cce83f2 100644 --- a/compiler/token.h +++ b/compiler/token.h @@ -185,6 +185,8 @@ enum TokenType { tok_clone, tok_instanceof, + tok_attribute_start, + tok_end }; diff --git a/tests/cpp/compiler/lexer-test.cpp b/tests/cpp/compiler/lexer-test.cpp index 5a437b887a..98587fe357 100644 --- a/tests/cpp/compiler/lexer-test.cpp +++ b/tests/cpp/compiler/lexer-test.cpp @@ -113,6 +113,13 @@ 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(;)"}}, + + // attributes + {"#[Attribute]", {"tok_attribute_start(#[)", "tok_func_name(Attribute)", "tok_clbrk(])"}}, + {"#[\nAttribute\n]", {"tok_attribute_start(#[)", "tok_func_name(Attribute)", "tok_clbrk(])"}}, + {"#[Attribute()]", {"tok_attribute_start(#[)", "tok_func_name(Attribute)", "tok_oppar(()", "tok_clpar())", "tok_clbrk(])"}}, + {"#[Attribute('name')]", {"tok_attribute_start(#[)", "tok_func_name(Attribute)", "tok_oppar(()", "tok_str(name)", "tok_clpar())", "tok_clbrk(])"}}, + {"#[Attribute(), Attribute2()]", {"tok_attribute_start(#[)", "tok_func_name(Attribute)", "tok_oppar(()", "tok_clpar())", "tok_comma(,)", "tok_func_name(Attribute2)", "tok_oppar(()", "tok_clpar())", "tok_clbrk(])"}}, }; for (const auto &test : tests) { diff --git a/tests/phpt/php8/attributes/001_class_attributes.php b/tests/phpt/php8/attributes/001_class_attributes.php new file mode 100644 index 0000000000..56ba5210e8 --- /dev/null +++ b/tests/phpt/php8/attributes/001_class_attributes.php @@ -0,0 +1,37 @@ +@ok php8 +