diff --git a/builtin-functions/_functions.txt b/builtin-functions/_functions.txt index 26ecdba031..d30ee7f448 100644 --- a/builtin-functions/_functions.txt +++ b/builtin-functions/_functions.txt @@ -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; diff --git a/compiler/data/class-data.h b/compiler/data/class-data.h index 0887dd2691..63027a2730 100644 --- a/compiler/data/class-data.h +++ b/compiler/data/class-data.h @@ -27,6 +27,7 @@ enum class ClassType { klass, interface, trait, + enumeration, ffi_scope, ffi_cdata, }; @@ -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; } diff --git a/compiler/debug.cpp b/compiler/debug.cpp index 795ed12fd9..56420d4118 100644 --- a/compiler/debug.cpp +++ b/compiler/debug.cpp @@ -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"}, diff --git a/compiler/gentree.cpp b/compiler/gentree.cpp index 11b70840e5..2985fd20cd 100644 --- a/compiler/gentree.cpp +++ b/compiler/gentree.cpp @@ -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'"); @@ -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(); @@ -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)) { @@ -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(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 c_alive(class_stack, cur_class, class_ptr); + StackPushPop 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::create(VertexUtil::create_with_str_val("value")); + value_param->type_hint = TypeHintPipe::create(std::vector{TypeHintPrimitive::create(tp_int), TypeHintPrimitive::create(tp_string)}); + auto params = VertexAdaptor::create(std::vector{value_param}); + + VertexPtr foreach_vert; + VertexAdaptor cases_call = VertexUtil::create_with_str_val("self::cases"); + + VertexAdaptor cur_case = VertexUtil::create_with_str_val("cur_case"); + + VertexAdaptor tmp_var = VertexUtil::create_with_str_val("tmp_var"); + tmp_var->extra_type = op_ex_var_superlocal; + + VertexAdaptor fe_param = VertexAdaptor::create(cases_call, cur_case, tmp_var); + + auto inst_prop = VertexUtil::create_with_str_val("value", cur_case.clone()); + auto cmp_vert = VertexAdaptor::create(VertexAdaptor::create(inst_prop.clone(), VertexUtil::create_with_str_val("value"))); + auto ret_vert = VertexUtil::embrace(VertexAdaptor::create(cur_case.clone())); + auto if_vert = VertexAdaptor::create(cmp_vert, ret_vert); + foreach_vert = VertexAdaptor::create(fe_param, VertexUtil::embrace(if_vert)); + + auto func_body = VertexUtil::embrace(foreach_vert); + + std::vector required_func_names = {replace_backslashes(cur_class->name) + "$$tryFrom", replace_backslashes(cur_class->name) + "$$from"}; + std::vector last_statements = { + VertexAdaptor::create(VertexAdaptor::create()), + [&]() { + VertexAdaptor text = VertexUtil::create_string_const(fmt_format("Not a valid backed value for enum \"{}\"", cur_class->name)); + auto code = VertexAdaptor::create(); + code->set_string("0"); + + VertexPtr node = gen_constructor_call_with_args("\\ValueError", std::vector{text, code}, auto_location()); + node = VertexAdaptor::create(node); + return node; + }() + }; + + for (size_t i = 0; i < required_func_names.size(); ++i) { + auto func = VertexAdaptor::create(params.clone(), func_body.clone()); + + std::vector next = func->cmd()->get_next(); + next.push_back(last_statements[i]); + func->cmd_ref() = VertexAdaptor::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 &cases) { + std::vector> arr_args; + + std::transform(cases.begin(), cases.end(), std::back_inserter(arr_args), [](const std::string &case_name) { + return VertexUtil::create_with_str_val("self::" + case_name); + }); + + auto response = VertexAdaptor::create(std::move(arr_args)); + + const auto params = VertexAdaptor::create(); + auto func = VertexAdaptor::create(params, VertexAdaptor::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::create(VertexUtil::create_with_str_val("name")); + auto params_vec = std::vector{name_param}; + + auto this_vertex = VertexAdaptor::create(); + this_vertex->str_val = "this"; + auto name_property = VertexAdaptor::create(this_vertex.clone()); + name_property->str_val = "name"; + auto ctor_body_seq = std::vector{VertexAdaptor::create(name_property, VertexUtil::create_with_str_val("name"))}; + + if (vk::any_of_equal(enum_type, EnumType::BackedString, EnumType::BackedInt)) { + auto value_param = VertexAdaptor::create(VertexUtil::create_with_str_val("value")); + params_vec.emplace_back(value_param); + + auto value_property = name_property.clone(); + value_property->str_val = "value"; + + ctor_body_seq.emplace_back(VertexAdaptor::create(value_property, VertexUtil::create_with_str_val("value"))); + } + + ctor_body_seq.emplace_back(VertexAdaptor::create(this_vertex.clone())); + + const auto body = VertexAdaptor::create(ctor_body_seq); + + auto func = VertexAdaptor::create(VertexAdaptor::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::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::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::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 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 cases; + const auto body_seq = body_vertex.try_as(); + + auto is_pure_enum_case = [](VertexAdaptor value) -> bool { + return value->args().empty(); + }; + + for (const auto &stmt: body_seq->args()) { + if (const auto case_vertex = stmt.try_as()) { + const auto case_name_vertex = case_vertex->expr().try_as(); + const auto case_name = case_name_vertex->get_string(); + cases.push_back(case_name); + + VertexAdaptor name_arg = VertexAdaptor::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 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()->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 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::create().set_location(auto_location()); + case_name->str_val = cur->str_val; + VertexAdaptor response; + + next_cur(); + + if (test_expect(tok_semicolon)) { + response = VertexAdaptor::create(case_name, VertexAdaptor::create()); + } else { + CE (!kphp_error(test_expect(tok_eq1), "\"=\" must be after backed enum case")); + response = VertexAdaptor::create(case_name, VertexAdaptor::create()); + + next_cur(); + + auto expr = get_expression(); + response = VertexAdaptor::create(case_name, VertexAdaptor::create(expr)); + } + + return response; +} VertexAdaptor GenTree::gen_constructor_call_with_args(const std::string &allocated_class_name, std::vector args, const Location &location) { auto alloc = VertexAdaptor::create().set_location(location); @@ -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: @@ -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()); diff --git a/compiler/gentree.h b/compiler/gentree.h index 31e5fe6314..95e0ff7d44 100644 --- a/compiler/gentree.h +++ b/compiler/gentree.h @@ -18,6 +18,13 @@ class GenTree { + enum class EnumType { + Empty, + Pure, + BackedString, + BackedInt, + }; + template class StackPushPop { std::vector &stack; @@ -119,8 +126,20 @@ class GenTree { static VertexAdaptor auto_capture_this_in_lambda(FunctionPtr f_lambda); VertexPtr get_class(const PhpDocComment *phpdoc, ClassType class_type); + void parse_extends(); + void parse_implements(); void parse_extends_implements(); + VertexPtr get_enum(const PhpDocComment *phpdoc); + VertexAdaptor get_enum_case(); + EnumType get_enum_type(); + std::vector get_enum_body_and_cases(EnumType enum_type); + void generate_enum_fields(EnumType enum_type); + void generate_enum_construct(EnumType enum_type); + void generate_pure_enum_methods(const std::vector &cases); + void generate_backed_enum_methods(); + + static VertexPtr process_arrow(VertexPtr lhs, VertexPtr rhs); private: diff --git a/compiler/keywords.gperf b/compiler/keywords.gperf index e1260987d0..21ba7123af 100644 --- a/compiler/keywords.gperf +++ b/compiler/keywords.gperf @@ -31,6 +31,7 @@ switch, tok_switch class, tok_class interface, tok_interface trait, tok_trait +enum, tok_enum const, tok_const default, tok_default do, tok_do diff --git a/compiler/pipes/code-gen.cpp b/compiler/pipes/code-gen.cpp index b039074221..4f1010adb4 100644 --- a/compiler/pipes/code-gen.cpp +++ b/compiler/pipes/code-gen.cpp @@ -72,6 +72,7 @@ void CodeGenF::on_finish(DataStream> &os) { } switch (c->class_type) { + case ClassType::enumeration: case ClassType::klass: code_gen_start_root_task(os, std::make_unique(c)); break; diff --git a/compiler/pipes/collect-required-and-classes.cpp b/compiler/pipes/collect-required-and-classes.cpp index 78305f26b8..1e5f47948d 100644 --- a/compiler/pipes/collect-required-and-classes.cpp +++ b/compiler/pipes/collect-required-and-classes.cpp @@ -74,6 +74,14 @@ class CollectRequiredPass final : public FunctionPassBase { } } + if (cur_class->name != "BackedEnum" && !cur_class->is_enum() && (dep.class_name == "UnitEnum" || dep.class_name == "BackedEnum")) { + if (cur_class->is_interface()) { + kphp_error(false, fmt_format("Interface {} cannot extend UnitEnum or BackedEnum", cur_class->name)); + } else { + kphp_error(false, fmt_format("Class {} cannot implement UnitEnum or BackedEnum", cur_class->name)); + } + } + require_class(dep.class_name); } // class constant values may contain other class constants that may need require_class() diff --git a/compiler/pipes/sort-and-inherit-classes.cpp b/compiler/pipes/sort-and-inherit-classes.cpp index 5cf22573fe..43df751da6 100644 --- a/compiler/pipes/sort-and-inherit-classes.cpp +++ b/compiler/pipes/sort-and-inherit-classes.cpp @@ -204,6 +204,8 @@ void SortAndInheritClassesF::inherit_child_class_from_parent(ClassPtr child_clas stage::set_file(child_class->file_id); stage::set_function(FunctionPtr{}); + kphp_error_return(!parent_class->is_enum(), fmt_format("Inheritance from enum is not allowed ({} extends {})", child_class->name, parent_class->name)); + if (parent_class->is_ffi_cdata() || parent_class->name == "FFI\\Scope") { kphp_error_return(child_class->is_builtin() || child_class->is_ffi_scope(), "FFI classes should not be extended"); @@ -283,6 +285,11 @@ void SortAndInheritClassesF::clone_members_from_traits(std::vector &&t traits[i]->members.for_each([&](ClassMemberInstanceMethod &m) { check_other_traits_doesnt_contain_method_and_clone(m.function); }); traits[i]->members.for_each([&](ClassMemberStaticMethod &m) { check_other_traits_doesnt_contain_method_and_clone(m.function); }); + if (ready_class->is_enum()) { + kphp_error_return(!traits[i]->members.has_any_instance_var() && !traits[i]->members.has_any_static_var(), + fmt_format("Traits used in an enum must not contain properties (enum {} uses trait {})", ready_class->name, traits[i]->name)); + } + traits[i]->members.for_each([&](const ClassMemberInstanceField &f) { ready_class->members.add_instance_field(f.root.clone(), f.var->init_val.clone(), f.modifiers, f.phpdoc, f.type_hint); }); @@ -319,6 +326,7 @@ void SortAndInheritClassesF::on_class_ready(ClassPtr klass, DataStreamget_class(dep.class_name); switch (dep.type) { + case ClassType::enumeration: case ClassType::klass: inherit_child_class_from_parent(klass, dep_class, function_stream); break; diff --git a/compiler/token.h b/compiler/token.h index 8303527035..9b229474da 100644 --- a/compiler/token.h +++ b/compiler/token.h @@ -47,6 +47,7 @@ enum TokenType { tok_class, tok_interface, tok_trait, + tok_enum, tok_extends, tok_implements, tok_namespace, diff --git a/compiler/vertex-util.h b/compiler/vertex-util.h index 24bedc2a5f..e5e79f11ed 100644 --- a/compiler/vertex-util.h +++ b/compiler/vertex-util.h @@ -26,6 +26,13 @@ class VertexUtil { 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); + template + static VertexAdaptor create_with_str_val(const std::string& str_val, Args&&... args) { + auto resp = VertexAdaptor::create(std::forward(args)...); + resp->str_val = str_val; + return resp; + } + static VertexPtr unwrap_array_value(VertexPtr v); static VertexPtr unwrap_string_value(VertexPtr v); static VertexPtr unwrap_int_value(VertexPtr v); diff --git a/runtime/enum.h b/runtime/enum.h new file mode 100644 index 0000000000..776098b388 --- /dev/null +++ b/runtime/enum.h @@ -0,0 +1,33 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2023 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + + +#include "runtime/memory_usage.h" +#include "runtime/refcountable_php_classes.h" +#include "runtime/to-array-processor.h" +#include "runtime/instance-copy-processor.h" + +struct C$UnitEnum : public abstract_refcountable_php_interface { + virtual const char *get_class() const noexcept = 0; + virtual int get_hash() const noexcept = 0; + + virtual void accept(ToArrayVisitor &) noexcept {} + virtual void accept(InstanceMemoryEstimateVisitor &) noexcept {} + virtual void accept(InstanceReferencesCountingVisitor &) noexcept {} + virtual void accept(InstanceDeepCopyVisitor &) noexcept {} + virtual void accept(InstanceDeepDestroyVisitor &) noexcept {} + + C$UnitEnum() __attribute__((always_inline)) = default; + ~C$UnitEnum() __attribute__((always_inline)) = default; +}; + +struct C$BackedEnum : public C$UnitEnum { + virtual const char *get_class() const noexcept override = 0; + virtual int get_hash() const noexcept override = 0; + + C$BackedEnum() __attribute__((always_inline)) = default; + ~C$BackedEnum() __attribute__((always_inline)) = default; +}; diff --git a/tests/phpt/enums/001_basic.php b/tests/phpt/enums/001_basic.php new file mode 100644 index 0000000000..a6ba582478 --- /dev/null +++ b/tests/phpt/enums/001_basic.php @@ -0,0 +1,16 @@ +@ok php8 +name; + +const c = MyBool::F; +echo c->name; + +echo MyBool::F->name; +echo MyBool::T->name; diff --git a/tests/phpt/enums/002_comparison.php b/tests/phpt/enums/002_comparison.php new file mode 100644 index 0000000000..1df80262c1 --- /dev/null +++ b/tests/phpt/enums/002_comparison.php @@ -0,0 +1,24 @@ +@ok php8 +name); +} + +enum Foo { + case Bar; + case Baz; +} + +enum Single { + case Single; +} + +enum EmptyEnum { +} + +var_dump(count(Foo::cases())); +var_dump(count(Single::cases())); +var_dump(count(EmptyEnum::cases())); diff --git a/tests/phpt/enums/004_instance_method.php b/tests/phpt/enums/004_instance_method.php new file mode 100644 index 0000000000..98df98f52a --- /dev/null +++ b/tests/phpt/enums/004_instance_method.php @@ -0,0 +1,14 @@ +@ok php8 +name); + } +} + +Foo::Bar->dump(); +Foo::Baz->dump(); diff --git a/tests/phpt/enums/005_const_context.php b/tests/phpt/enums/005_const_context.php new file mode 100644 index 0000000000..e357c02f17 --- /dev/null +++ b/tests/phpt/enums/005_const_context.php @@ -0,0 +1,16 @@ +@ok php8 +name); + } +} + +const CONST_VAR = Foo::Bar; +$var = Foo::Baz; + +CONST_VAR->dump(); +$var->dump(); diff --git a/tests/phpt/enums/006_func_param.php b/tests/phpt/enums/006_func_param.php new file mode 100644 index 0000000000..7a35a69980 --- /dev/null +++ b/tests/phpt/enums/006_func_param.php @@ -0,0 +1,17 @@ +@ok php8 +name); + } +} + +function print_Foo(Foo $var) { + $var->dump(); +} + +print_Foo(Foo::Bar); +print_Foo(Foo::Baz); diff --git a/tests/phpt/enums/007_instanceof.php b/tests/phpt/enums/007_instanceof.php new file mode 100644 index 0000000000..5303f9a33c --- /dev/null +++ b/tests/phpt/enums/007_instanceof.php @@ -0,0 +1,16 @@ +@ok php8 +print(); +MyBool::F->print(); diff --git a/tests/phpt/enums/009_namespace_enum.php b/tests/phpt/enums/009_namespace_enum.php new file mode 100644 index 0000000000..54c6bbb937 --- /dev/null +++ b/tests/phpt/enums/009_namespace_enum.php @@ -0,0 +1,9 @@ +@ok php8 +name; +echo AnotherBool::F->name; diff --git a/tests/phpt/enums/010_with_traits.php b/tests/phpt/enums/010_with_traits.php new file mode 100644 index 0000000000..4fcb11e53b --- /dev/null +++ b/tests/phpt/enums/010_with_traits.php @@ -0,0 +1,18 @@ +@ok php8 +log("sample"); diff --git a/tests/phpt/enums/011_unit_enum.php b/tests/phpt/enums/011_unit_enum.php new file mode 100644 index 0000000000..785d7d5fb7 --- /dev/null +++ b/tests/phpt/enums/011_unit_enum.php @@ -0,0 +1,15 @@ +@ok php8 +name; + echo $v->value; +} + +$v = HttpResponse::Ok; +var_dump($v->value); +var_dump($v->name); + +const c = HttpResponse::ServerError; +var_dump($v->value); +var_dump($v->name); diff --git a/tests/phpt/enums/013_backed_try_from.php b/tests/phpt/enums/013_backed_try_from.php new file mode 100644 index 0000000000..497cc171be --- /dev/null +++ b/tests/phpt/enums/013_backed_try_from.php @@ -0,0 +1,13 @@ +@ok php8 +name); +var_dump(Status::tryFrom(404)->name); +var_dump(Status::tryFrom(500)->name); +var_dump(Status::tryFrom(0) === null); diff --git a/tests/phpt/enums/014_backed_from.php b/tests/phpt/enums/014_backed_from.php new file mode 100644 index 0000000000..5ad7d34e1e --- /dev/null +++ b/tests/phpt/enums/014_backed_from.php @@ -0,0 +1,12 @@ +@ok php8 +name); +var_dump(Status::from(404)->name); +var_dump(Status::from(500)->name); diff --git a/tests/phpt/enums/015_failed_from.php b/tests/phpt/enums/015_failed_from.php new file mode 100644 index 0000000000..10ac364124 --- /dev/null +++ b/tests/phpt/enums/015_failed_from.php @@ -0,0 +1,13 @@ +@ok php8 +getCode() . "\n"; +} diff --git a/tests/phpt/enums/016_backed_enum_interface.php b/tests/phpt/enums/016_backed_enum_interface.php new file mode 100644 index 0000000000..6a86462dac --- /dev/null +++ b/tests/phpt/enums/016_backed_enum_interface.php @@ -0,0 +1,17 @@ +@ok php8 +Print(); diff --git a/tests/phpt/enums/104_extends_enum.php b/tests/phpt/enums/104_extends_enum.php new file mode 100644 index 0000000000..3d95b2a867 --- /dev/null +++ b/tests/phpt/enums/104_extends_enum.php @@ -0,0 +1,9 @@ +@kphp_should_fail +