Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
14fc348
Add php8* versions in python tests
mkornaukhov Apr 21, 2023
69ad8e5
Add php8* versions in phpt
mkornaukhov Apr 21, 2023
f7a58f9
Add wrap in InlineDefinesUsage
mkornaukhov Apr 24, 2023
deaf855
Fix errors in CI
mkornaukhov Apr 25, 2023
eb23d46
Fix
mkornaukhov Apr 25, 2023
613a33f
Allow constructors with const args to be `cnst_const_val`
mkornaukhov May 1, 2023
8960d90
Add name generating
mkornaukhov May 2, 2023
6707600
Fix includes and externs
mkornaukhov May 2, 2023
9ac1a5a
Fix includes x2
mkornaukhov May 2, 2023
ee96d5d
Tests & c-tor as const
mkornaukhov May 3, 2023
4db2355
Fix includes x3
mkornaukhov May 3, 2023
c697c19
Add inlined defines counter in CalcConstTypes pass
mkornaukhov May 4, 2023
2838e78
Revert implicit_const_vars
mkornaukhov May 4, 2023
4c85275
Revert "Add php8* versions in phpt"
mkornaukhov May 4, 2023
95b8581
Revert "Add php8* versions in python tests"
mkornaukhov May 4, 2023
c78e98e
Fix php versions
mkornaukhov May 4, 2023
bd9ed5d
Fix deduce (string const) -> callable
mkornaukhov May 4, 2023
1bc8ef3
Fix const as default vars
mkornaukhov May 4, 2023
50fcbe8
Fix eq3 optimization with const objects
mkornaukhov May 11, 2023
30f5369
Fix eq3 optimization with const objects x2
mkornaukhov May 11, 2023
91ebefd
Fix runtime rule
mkornaukhov May 12, 2023
0f24f06
Fix tests
mkornaukhov May 12, 2023
edff402
Remove debug prints
mkornaukhov May 12, 2023
ea2361d
Fix review notes
mkornaukhov May 12, 2023
68952c7
Fix arrow access for const objects
mkornaukhov May 15, 2023
3b3c58d
Fix review notes
mkornaukhov May 16, 2023
d6cc98e
Implement basic things
mkornaukhov May 15, 2023
229599a
Add first test
mkornaukhov May 15, 2023
d8711f7
Add tests; Add `implements; Fix problem with namespaces
mkornaukhov May 15, 2023
104a70d
Add test with trait
mkornaukhov May 15, 2023
70afe97
Add UnitEnum to runtime
mkornaukhov May 16, 2023
3bacdb8
Improve diagnostics
mkornaukhov May 17, 2023
20af47f
Add backed enum
mkornaukhov May 19, 2023
a32bc0e
Improve diagnostics
mkornaukhov May 19, 2023
546a472
Small fix
mkornaukhov May 26, 2023
14a0e9b
Merge branch 'master' of github.com:VKCOM/kphp into mkornaukhov03/enum2
mkornaukhov May 26, 2023
5ed328c
Refactoring x1
mkornaukhov May 26, 2023
ee32b31
Small fix
mkornaukhov May 29, 2023
0f22e0a
Refactor gentree.cpp
mkornaukhov May 29, 2023
a08acea
Small fixes
mkornaukhov May 29, 2023
d9bc01e
Fix bug with sharing same nodes
mkornaukhov May 31, 2023
7d3f638
Fix bug with undefined constants
mkornaukhov Jun 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions builtin-functions/_functions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ class Error implements Throwable {
final private function __clone() ::: void;
}

interface UnitEnum {
public static function cases();
}

interface BackedEnum implements UnitEnum {
public static function from(int | string $value);
public static function tryFrom(int | string $value);
}

interface Memcache {
public function get (string|string[] $key) ::: mixed;
public function delete (string $key) ::: bool;
Expand Down
2 changes: 2 additions & 0 deletions compiler/data/class-data.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ enum class ClassType {
klass,
interface,
trait,
enumeration,
ffi_scope,
ffi_cdata,
};
Expand Down Expand Up @@ -122,6 +123,7 @@ class ClassData : public Lockable {
}

bool is_class() const { return class_type == ClassType::klass; }
bool is_enum() const { return class_type == ClassType::enumeration; }
bool is_interface() const { return class_type == ClassType::interface; }
bool is_trait() const { return class_type == ClassType::trait; }
bool is_ffi_scope() const { return class_type == ClassType::ffi_scope; }
Expand Down
1 change: 1 addition & 0 deletions compiler/debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ std::string debugTokenName(TokenType t) {
{tok_class, "tok_class"},
{tok_interface, "tok_interface"},
{tok_trait, "tok_trait"},
{tok_enum, "tok_enum"},
{tok_extends, "tok_extends"},
{tok_implements, "tok_implements"},
{tok_namespace, "tok_namespace"},
Expand Down
317 changes: 313 additions & 4 deletions compiler/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1644,9 +1644,8 @@ bool GenTree::check_statement_end() {
}
return expect (tok_semicolon, "';'");
}

void GenTree::parse_extends_implements() {
if (test_expect(tok_extends)) { // extends comes before 'implements', the order is fixed
void GenTree::parse_extends() {
if (test_expect(tok_extends)) {
do {
next_cur(); // (same as in PHP)
kphp_error_return(test_expect(tok_func_name), "Class name expected after 'extends'");
Expand All @@ -1655,7 +1654,8 @@ void GenTree::parse_extends_implements() {
next_cur();
} while (cur_class->is_interface() && test_expect(tok_comma));
}

}
void GenTree::parse_implements() {
if (test_expect(tok_implements)) {
do {
next_cur();
Expand All @@ -1666,6 +1666,11 @@ void GenTree::parse_extends_implements() {
}
}

void GenTree::parse_extends_implements() {
parse_extends(); // extends comes before 'implements', the order is fixed
parse_implements();
}

VertexPtr GenTree::get_class(const PhpDocComment *phpdoc, ClassType class_type) {
ClassModifiers modifiers;
if (test_expect(tok_abstract)) {
Expand Down Expand Up @@ -1744,6 +1749,306 @@ VertexPtr GenTree::get_class(const PhpDocComment *phpdoc, ClassType class_type)
return {};
}

VertexPtr GenTree::get_enum(const PhpDocComment *phpdoc) {
CE(cur->type() == tok_enum);
next_cur();

CE (!kphp_error(test_expect(tok_func_name), "Enum name expected"));

const auto name_str = static_cast<std::string>(cur->str_val);
std::string full_class_name = processing_file->namespace_name.empty() ? std::string{name_str} : processing_file->namespace_name + "\\" + name_str;

kphp_error(processing_file->namespace_uses.find(name_str) == processing_file->namespace_uses.end(),
"Enum name is the same as one of 'use' at the top of the file");


const auto class_ptr = ClassPtr(new ClassData{ClassType::enumeration});
StackPushPop<ClassPtr> c_alive(class_stack, cur_class, class_ptr);
StackPushPop<FunctionPtr> f_alive(functions_stack, cur_function, cur_class->gen_holder_function(full_class_name));

next_cur();
kphp_error(!test_expect(tok_extends), "Enums cannot extend");

EnumType enum_type = get_enum_type();

parse_implements();

cur_class->modifiers.set_final();
cur_class->file_id = processing_file;
cur_class->set_name_and_src_name(full_class_name);
cur_class->phpdoc = phpdoc;
cur_class->is_immutable = true;
cur_class->location_line_num = line_num;
cur_class->add_str_dependent(cur_function, ClassType::interface, enum_type == EnumType::Pure ? "\\UnitEnum" : "\\BackedEnum");

cur_class->add_class_constant();


auto cases = get_enum_body_and_cases(enum_type);

generate_enum_fields(enum_type);
generate_enum_construct(enum_type);

generate_pure_enum_methods(cases);

if (vk::any_of_equal(enum_type, EnumType::BackedInt, EnumType::BackedString)) {
generate_backed_enum_methods();
}

bool registered = G->register_class(cur_class);
if (registered) {
++G->stats.total_classes;
}
if (registered) {
G->register_and_require_function(cur_function, parsed_os, true);
}

return {};
}

void GenTree::generate_backed_enum_methods() {
auto value_param = VertexAdaptor<op_func_param>::create(VertexUtil::create_with_str_val<op_var>("value"));
value_param->type_hint = TypeHintPipe::create(std::vector<const TypeHint *>{TypeHintPrimitive::create(tp_int), TypeHintPrimitive::create(tp_string)});
auto params = VertexAdaptor<op_func_param_list>::create(std::vector<VertexPtr>{value_param});

VertexPtr foreach_vert;
VertexAdaptor<op_func_call> cases_call = VertexUtil::create_with_str_val<op_func_call>("self::cases");

VertexAdaptor<op_var> cur_case = VertexUtil::create_with_str_val<op_var>("cur_case");

VertexAdaptor<op_var> tmp_var = VertexUtil::create_with_str_val<op_var>("tmp_var");
tmp_var->extra_type = op_ex_var_superlocal;

VertexAdaptor<op_foreach_param> fe_param = VertexAdaptor<op_foreach_param>::create(cases_call, cur_case, tmp_var);

auto inst_prop = VertexUtil::create_with_str_val<op_instance_prop>("value", cur_case.clone());
auto cmp_vert = VertexAdaptor<op_conv_bool>::create(VertexAdaptor<op_eq3>::create(inst_prop.clone(), VertexUtil::create_with_str_val<op_var>("value")));
auto ret_vert = VertexUtil::embrace(VertexAdaptor<op_return>::create(cur_case.clone()));
auto if_vert = VertexAdaptor<op_if>::create(cmp_vert, ret_vert);
foreach_vert = VertexAdaptor<op_foreach>::create(fe_param, VertexUtil::embrace(if_vert));

auto func_body = VertexUtil::embrace(foreach_vert);

std::vector<std::string> required_func_names = {replace_backslashes(cur_class->name) + "$$tryFrom", replace_backslashes(cur_class->name) + "$$from"};
std::vector<VertexPtr> last_statements = {
VertexAdaptor<op_return>::create(VertexAdaptor<op_null>::create()),
[&]() {
VertexAdaptor<op_string> text = VertexUtil::create_string_const(fmt_format("Not a valid backed value for enum \"{}\"", cur_class->name));
auto code = VertexAdaptor<op_int_const>::create();
code->set_string("0");

VertexPtr node = gen_constructor_call_with_args("\\ValueError", std::vector<VertexPtr>{text, code}, auto_location());
node = VertexAdaptor<op_throw>::create(node);
return node;
}()
};

for (size_t i = 0; i < required_func_names.size(); ++i) {
auto func = VertexAdaptor<op_function>::create(params.clone(), func_body.clone());

std::vector<VertexPtr> next = func->cmd()->get_next();
next.push_back(last_statements[i]);
func->cmd_ref() = VertexAdaptor<op_seq>::create(next);

auto func_data = FunctionData::create_function(std::move(required_func_names[i]), func, FunctionData::func_local);
func_data->update_location_in_body();
func_data->is_inline = true;
func_data->modifiers = FunctionModifiers::nonmember();
func_data->modifiers.set_public();
func_data->modifiers.set_final();
cur_class->members.add_static_method(func_data);
G->register_and_require_function(func_data, parsed_os, true);
}
}

void GenTree::generate_pure_enum_methods(const std::vector<std::string> &cases) {
std::vector<VertexAdaptor<op_func_name>> arr_args;

std::transform(cases.begin(), cases.end(), std::back_inserter(arr_args), [](const std::string &case_name) {
return VertexUtil::create_with_str_val<op_func_name>("self::" + case_name);
});

auto response = VertexAdaptor<op_array>::create(std::move(arr_args));

const auto params = VertexAdaptor<op_func_param_list>::create();
auto func = VertexAdaptor<op_function>::create(params, VertexAdaptor<op_seq>::create());
VertexUtil::func_force_return(func, response);
const std::string func_name = replace_backslashes(cur_class->name) + "$$cases";

auto cases_fun = FunctionData::create_function(func_name, func, FunctionData::func_local);

cases_fun->update_location_in_body();
cases_fun->is_inline = true;
cases_fun->modifiers = FunctionModifiers::nonmember();
cases_fun->modifiers.set_public();
cases_fun->modifiers.set_final();
cases_fun->return_typehint = TypeHintArray::create(cur_class->type_hint);
cur_class->members.add_static_method(cases_fun);
G->register_and_require_function(cases_fun, parsed_os, true);
}

void GenTree::generate_enum_construct(EnumType enum_type) {
auto name_param = VertexAdaptor<op_func_param>::create(VertexUtil::create_with_str_val<op_var>("name"));
auto params_vec = std::vector<VertexPtr>{name_param};

auto this_vertex = VertexAdaptor<op_var>::create();
this_vertex->str_val = "this";
auto name_property = VertexAdaptor<op_instance_prop>::create(this_vertex.clone());
name_property->str_val = "name";
auto ctor_body_seq = std::vector<VertexPtr>{VertexAdaptor<op_set>::create(name_property, VertexUtil::create_with_str_val<op_var>("name"))};

if (vk::any_of_equal(enum_type, EnumType::BackedString, EnumType::BackedInt)) {
auto value_param = VertexAdaptor<op_func_param>::create(VertexUtil::create_with_str_val<op_var>("value"));
params_vec.emplace_back(value_param);

auto value_property = name_property.clone();
value_property->str_val = "value";

ctor_body_seq.emplace_back(VertexAdaptor<op_set>::create(value_property, VertexUtil::create_with_str_val<op_var>("value")));
}

ctor_body_seq.emplace_back(VertexAdaptor<op_return>::create(this_vertex.clone()));

const auto body = VertexAdaptor<op_seq>::create(ctor_body_seq);

auto func = VertexAdaptor<op_function>::create(VertexAdaptor<op_func_param_list>::create(params_vec), body);

std::string func_name = replace_backslashes(cur_class->name) + "$$" + ClassData::NAME_OF_CONSTRUCT;
auto this_param = cur_class->gen_param_this(func->get_location());
func->param_list_ref() = VertexAdaptor<op_func_param_list>::create(this_param, func->param_list()->params());
VertexUtil::func_force_return(func, cur_class->gen_vertex_this(func->location));

auto ctor_function = FunctionData::create_function(func_name, func, FunctionData::func_local);
ctor_function->update_location_in_body();
ctor_function->is_inline = true;
ctor_function->modifiers = FunctionModifiers::instance_public();
cur_class->members.add_instance_method(ctor_function);
G->register_and_require_function(ctor_function, parsed_os, true);
}

void GenTree::generate_enum_fields(EnumType enum_type) {
if (vk::any_of_equal(enum_type, EnumType::BackedString, EnumType::BackedInt)) {
auto modifiers_field_public = FieldModifiers();
modifiers_field_public.set_public();

auto value_vertex = VertexAdaptor<op_var>::create();
value_vertex->str_val = "value";
value_vertex->is_const = true;

cur_class->members.add_instance_field(value_vertex,
VertexPtr{},
modifiers_field_public,
nullptr,
TypeHintPrimitive::create(enum_type == EnumType::BackedInt ? tp_int : tp_string));
}
auto modifiers_field_public = FieldModifiers();
modifiers_field_public.set_public();

auto name_vertex = VertexAdaptor<op_var>::create();
name_vertex->str_val = "name";
name_vertex->is_const = true;
cur_class->members.add_instance_field(name_vertex,
VertexPtr{},
modifiers_field_public,
nullptr,
TypeHintPrimitive::create(PrimitiveType::tp_string));

}

std::vector<std::string> GenTree::get_enum_body_and_cases(EnumType enum_type) {
CE(cur->type() == tok_opbrc);

VertexPtr body_vertex = get_statement();
kphp_error(body_vertex && body_vertex->type() == op_seq, "Incorrect enum body");

std::vector<std::string> cases;
const auto body_seq = body_vertex.try_as<op_seq>();

auto is_pure_enum_case = [](VertexAdaptor<op_seq> value) -> bool {
return value->args().empty();
};

for (const auto &stmt: body_seq->args()) {
if (const auto case_vertex = stmt.try_as<op_case>()) {
const auto case_name_vertex = case_vertex->expr().try_as<op_func_name>();
const auto case_name = case_name_vertex->get_string();
cases.push_back(case_name);

VertexAdaptor<op_string> name_arg = VertexAdaptor<op_string>::create();
name_arg->str_val = case_name;

const bool is_pure_case = is_pure_enum_case(case_vertex->cmd());

kphp_error(
(is_pure_case && enum_type == EnumType::Pure) ||
(!is_pure_case && vk::any_of_equal(enum_type, EnumType::BackedInt, EnumType::BackedString)),
fmt_format("Not appropriate case \"{}\" for enum \"{}\"", case_name, cur_class->name));

std::vector<VertexPtr> args{name_arg};
if (vk::any_of_equal(enum_type, EnumType::BackedString, EnumType::BackedInt)) {
kphp_error(!case_vertex->cmd()->args().empty(), "Cases without values are not allowed in backed enum");
args.push_back(case_vertex->cmd()->args()[0]);
}

const auto ctor_call = gen_constructor_call_with_args(cur_class->name, args, auto_location());
ctor_call->args()[0].as<op_alloc>()->allocated_class_name = "self";
cur_class->members.add_constant(case_name, ctor_call, AccessModifiers::public_);
}
kphp_error(stmt->type() != op_var, "Fields are not allowed in enums");
}
return cases;
}

GenTree::EnumType GenTree::get_enum_type() {
kphp_assert_msg(cur_class && cur_class->is_enum(), "Cannot calculate enum type for non-enum");
if (test_expect(tok_colon)) {
next_cur();

if (cur->type() == tok_int) {
next_cur();
return EnumType::BackedInt;
} else if (cur->type() == tok_string) {
next_cur();
return EnumType::BackedString;
}
kphp_error(false, fmt_format("Invalid type of enum {}", cur_class->name));
}
return EnumType::Pure;
}

VertexAdaptor<op_case> GenTree::get_enum_case() {
/*
enum {
...
case ENUM_CASE (= "value"); <----- parse such a construction
}
*/
CE(cur->type() == tok_case);

kphp_error(cur_class->is_enum(), "'case' expressions are available only in enum declaration");

next_cur();
CE (!kphp_error(test_expect(tok_func_name), "Enum case name expected"));
auto case_name = VertexAdaptor<op_func_name>::create().set_location(auto_location());
case_name->str_val = cur->str_val;
VertexAdaptor<op_case> response;

next_cur();

if (test_expect(tok_semicolon)) {
response = VertexAdaptor<op_case>::create(case_name, VertexAdaptor<op_seq>::create());
} else {
CE (!kphp_error(test_expect(tok_eq1), "\"=\" must be after backed enum case"));
response = VertexAdaptor<op_case>::create(case_name, VertexAdaptor<op_seq>::create());

next_cur();

auto expr = get_expression();
response = VertexAdaptor<op_case>::create(case_name, VertexAdaptor<op_seq>::create(expr));
}

return response;
}

VertexAdaptor<op_func_call> GenTree::gen_constructor_call_with_args(const std::string &allocated_class_name, std::vector<VertexPtr> args, const Location &location) {
auto alloc = VertexAdaptor<op_alloc>::create().set_location(location);
Expand Down Expand Up @@ -2021,6 +2326,8 @@ VertexPtr GenTree::get_statement(const PhpDocComment *phpdoc) {
CE (check_seq_end());
return res;
}
case tok_case:
return get_enum_case();
case tok_return:
return get_return();
case tok_continue:
Expand Down Expand Up @@ -2232,6 +2539,8 @@ VertexPtr GenTree::get_statement(const PhpDocComment *phpdoc) {
kphp_error(processing_file->namespace_uses.empty(), "Usage of operator `use`(Aliasing/Importing) with traits is temporarily prohibited");
return res;
}
case tok_enum:
return get_enum(phpdoc);
default: {
auto res = get_expression();
CE (check_statement_end());
Expand Down
Loading