Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added support for final constants #336

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion compiler/data/class-data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ FunctionPtr ClassData::add_virt_clone() {
}

void ClassData::add_class_constant() {
members.add_constant("class", GenTree::generate_constant_field_class_value(get_self()), AccessModifiers::public_);
members.add_constant("class", GenTree::generate_constant_field_class_value(get_self()), AccessModifiers::public_, false);
}

void ClassData::create_constructor_with_parent_call(DataStream<FunctionPtr> &os) {
Expand Down
10 changes: 6 additions & 4 deletions compiler/data/class-members.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,11 @@ const TypeData *ClassMemberInstanceField::get_inferred_type() const {
return tinf::get_type(var);
}

inline ClassMemberConstant::ClassMemberConstant(ClassPtr klass, const string &const_name, VertexPtr value, AccessModifiers access) :
inline ClassMemberConstant::ClassMemberConstant(ClassPtr klass, const string &const_name, VertexPtr value, AccessModifiers access, bool is_final) :
klass(klass),
value(value),
access(access) {
access(access),
is_final(is_final) {
define_name = "c#" + replace_backslashes(klass->name) + "$$" + const_name;
}

Expand Down Expand Up @@ -182,8 +184,8 @@ void ClassMembersContainer::add_instance_field(VertexAdaptor<op_var> root, Verte
append_member(ClassMemberInstanceField{klass, root, def_val, modifiers, phpdoc_str, type_hint});
}

void ClassMembersContainer::add_constant(const std::string &const_name, VertexPtr value, AccessModifiers access) {
append_member(ClassMemberConstant{klass, const_name, value, access});
void ClassMembersContainer::add_constant(const std::string &const_name, VertexPtr value, AccessModifiers access, bool is_final) {
append_member(ClassMemberConstant{klass, const_name, value, access, is_final});
}

void ClassMembersContainer::safe_add_instance_method(FunctionPtr function) {
Expand Down
6 changes: 4 additions & 2 deletions compiler/data/class-members.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,12 @@ struct ClassMemberInstanceField {

struct ClassMemberConstant {
std::string define_name;
ClassPtr klass;
VertexPtr value;
AccessModifiers access;
bool is_final;

ClassMemberConstant(ClassPtr klass, const std::string &const_name, VertexPtr value, AccessModifiers access);
ClassMemberConstant(ClassPtr klass, const std::string &const_name, VertexPtr value, AccessModifiers access, bool is_final);

vk::string_view global_name() const &;
vk::string_view global_name() const && = delete;
Expand Down Expand Up @@ -191,7 +193,7 @@ class ClassMembersContainer {
void add_instance_method(FunctionPtr function);
void add_static_field(VertexAdaptor<op_var> root, VertexPtr def_val, FieldModifiers modifiers, vk::string_view phpdoc_str, const TypeHint *type_hint);
void add_instance_field(VertexAdaptor<op_var> root, VertexPtr def_val, FieldModifiers modifiers, vk::string_view phpdoc_str, const TypeHint *type_hint);
void add_constant(const std::string &const_name, VertexPtr value, AccessModifiers access);
void add_constant(const std::string &const_name, VertexPtr value, AccessModifiers access, bool is_final);

void safe_add_instance_method(FunctionPtr function);

Expand Down
71 changes: 37 additions & 34 deletions compiler/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1470,6 +1470,10 @@ VertexPtr GenTree::get_class_member(vk::string_view phpdoc_str) {
modifiers.set_public();
}

if (test_expect(tok_const)) {
return get_class_const(modifiers);
}

if (test_expect(tok_function)) {
return get_function(tok_function, phpdoc_str, modifiers);
}
Expand All @@ -1486,7 +1490,7 @@ VertexPtr GenTree::get_class_member(vk::string_view phpdoc_str) {
return {};
}

kphp_error(0, "Expected 'function' or $var_name after class member modifiers");
kphp_error(0, "Expected 'function', 'const' or $var_name after class member modifiers");
next_cur();
return {};
}
Expand Down Expand Up @@ -2064,18 +2068,6 @@ VertexPtr GenTree::get_statement(vk::string_view phpdoc_str) {
case tok_protected:
case tok_public:
case tok_private:
if (std::next(cur, 1)->type() == tok_const) {
next_cur();

auto access = AccessModifiers::public_;
if (type == tok_protected) {
access = AccessModifiers::protected_;
} else if (type == tok_private) {
access = AccessModifiers::private_;
}
return get_const_after_explicit_access_modifier(access);
}
// fall through
case tok_final:
case tok_abstract:
if (cur_function->type == FunctionData::func_class_holder) {
Expand Down Expand Up @@ -2231,39 +2223,50 @@ VertexPtr GenTree::get_statement(vk::string_view phpdoc_str) {
kphp_fail();
}

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()) {
CE(!kphp_error(access == AccessModifiers::public_, "const access modifier in interface must be public"));
VertexPtr GenTree::get_class_const(ClassMemberModifiers modifiers) {
CE(!kphp_error(!modifiers.is_static(), fmt_format("Cannot use 'static' as constant modifier")));
CE(!kphp_error(!modifiers.is_abstract(), fmt_format("Cannot use 'abstract' as constant modifier")));

if (modifiers.is_private() && modifiers.is_final()) {
kphp_error(0, "Private constant cannot be final as it is not visible to other classes");
}

return get_const(access);
if (cur_class->is_interface() && modifiers.access_modifier() != AccessModifiers::public_) {
CE(!kphp_error(0, fmt_format("Access type for interface constant must be public")));
}

return get_const(modifiers.access_modifier(), modifiers.is_final());
}

VertexPtr GenTree::get_const(AccessModifiers access) {
auto location = auto_location();
VertexPtr GenTree::get_const(AccessModifiers access, bool is_final) {
const auto location = auto_location();
next_cur();

bool const_in_global_scope = functions_stack.size() == 1 && !cur_class;
bool const_in_class = !!cur_class;
const auto in_class = (bool)cur_class;
const auto in_global_scope = functions_stack.size() == 1 && !in_class;

CE (!kphp_error(const_in_global_scope || const_in_class, "const expressions supported only inside classes and namespaces or in global scope"));
std::string const_name = get_identifier();
CE (!const_name.empty());
kphp_error(const_name != "class", "A class constant must not be called 'class'; it is reserved for class name fetching");
if (!in_global_scope && !in_class) {
kphp_error(0, "Constant definition supported only inside classes and namespaces or in global scope");
}

CE (expect(tok_eq1, "'='"));
VertexPtr v = get_expression();
CE (check_statement_end());
const auto name = get_identifier();
CE(!name.empty());
if (name == "class") {
kphp_error(0, "A class constant must not be called 'class'; it is reserved for class name fetching");
}

CE(expect(tok_eq1, "'='"));
const auto value_expr = get_expression();
CE(check_statement_end());

if (const_in_class) {
cur_class->members.add_constant(const_name, v, access);
if (in_class) {
cur_class->members.add_constant(name, value_expr, access, is_final);
return VertexAdaptor<op_empty>::create();
}

auto name = VertexAdaptor<op_string>::create();
name->str_val = const_name;
return VertexAdaptor<op_define>::create(name, v).set_location(location);
auto name_vertex = VertexAdaptor<op_string>::create();
name_vertex->str_val = name;
return VertexAdaptor<op_define>::create(name_vertex, value_expr).set_location(location);
}

void GenTree::get_instance_var_list(vk::string_view phpdoc_str, FieldModifiers modifiers, const TypeHint *type_hint) {
Expand Down
4 changes: 2 additions & 2 deletions compiler/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ class GenTree {
lambda_generators.clear();
}

VertexPtr get_const_after_explicit_access_modifier(AccessModifiers access);
VertexPtr get_const(AccessModifiers access);
VertexPtr get_class_const(ClassMemberModifiers modifiers);
VertexPtr get_const(AccessModifiers access, bool is_final = false);

public:
int line_num{0};
Expand Down
24 changes: 20 additions & 4 deletions compiler/pipes/sort-and-inherit-classes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -280,10 +280,9 @@ void SortAndInheritClassesF::inherit_child_class_from_parent(ClassPtr child_clas
);
}
});
parent_class->members.for_each([&](const ClassMemberConstant &c) {
if (auto child_const = child_class->get_constant(c.local_name())) {
kphp_error(child_const->access == c.access,
fmt_format("Can't change access type for constant {} in class {}", c.local_name(), child_class->name));
parent_class->members.for_each([&](ClassMemberConstant &parent_const) {
if (auto child_const = child_class->get_constant(parent_const.local_name())) {
check_const_inheritance(child_class, parent_class, &parent_const, child_const);
}
});
}
Expand All @@ -300,10 +299,27 @@ void SortAndInheritClassesF::inherit_class_from_interface(ClassPtr child_class,

child_class->implements.emplace_back(interface_class);

interface_class->members.for_each([&](const ClassMemberConstant &parent_const) {
if (auto child_const = child_class->get_constant(parent_const.local_name())) {
check_const_inheritance(child_class, interface_class, &parent_const, child_const);
}
});

AutoLocker<Lockable *> locker(&(*interface_class));
interface_class->derived_classes.emplace_back(child_class);
}

void SortAndInheritClassesF::check_const_inheritance(ClassPtr child_class, ClassPtr parent_class, const ClassMemberConstant *parent_const, const ClassMemberConstant *child_const) {
if (parent_const->is_final && child_const->klass == child_class) {
const auto child_name = child_class->name + "::" + child_const->local_name();
const auto parent_name = parent_class->name + "::" + parent_const->local_name();
kphp_error(0, fmt_format("{} cannot override final constant {}", child_name, parent_name));
}

kphp_error(child_const->access == parent_const->access,
fmt_format("Can't change access type for constant {} in class {}", parent_const->local_name(), child_class->name));
}

void SortAndInheritClassesF::clone_members_from_traits(std::vector<TraitPtr> &&traits, ClassPtr ready_class, DataStream<FunctionPtr> &function_stream) {
for (size_t i = 0; i < traits.size(); ++i) {
auto check_other_traits_doesnt_contain_method_and_clone = [&](FunctionPtr method) {
Expand Down
2 changes: 2 additions & 0 deletions compiler/pipes/sort-and-inherit-classes.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class SortAndInheritClassesF {

decltype(ht)::HTNode *get_not_ready_dependency(ClassPtr klass);

static void check_const_inheritance(ClassPtr child_class, ClassPtr parent_class, const ClassMemberConstant *parent_const, const ClassMemberConstant *child_const);

public:
void execute(ClassPtr klass, MultipleDataStreams<FunctionPtr, ClassPtr> &os);
static void check_on_finish(DataStream<FunctionPtr> &os);
Expand Down
4 changes: 2 additions & 2 deletions tests/phpt/access/constants/04_interface.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@kphp_should_fail
/const access modifier in interface must be public/
/Access type for interface constant must be public/
<?php
interface IProtected {
protected const PROT = 10;
Expand All @@ -16,4 +16,4 @@ public function empty() {};
}

$a = new A();
$b = new B();
$b = new B();
7 changes: 7 additions & 0 deletions tests/phpt/php8/final_constants/001_final_constants.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@ok php8
<?php

class Foo {
final const A = "foo";
final public const B = "foo";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@ok php8
<?php

interface I1 {
const C = 1;
}

interface I2 {
const C = 2;
}

interface I3 extends I1, I2 {
const C = 3;
}

class F implements I3 {}

echo F::C;
11 changes: 11 additions & 0 deletions tests/phpt/php8/final_constants/003_redeclaration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@kphp_should_fail php8
/Bar::A cannot override final constant Foo::A/
<?php

class Foo {
final const A = "foo";
}

class Bar extends Foo {
const A = "bar";
}
7 changes: 7 additions & 0 deletions tests/phpt/php8/final_constants/004_private_final.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@kphp_should_fail php8
/Private constant cannot be final as it is not visible to other classes/
<?php

class Foo {
private final const A = "foo";
}
10 changes: 10 additions & 0 deletions tests/phpt/php8/final_constants/005_redeclaration_in_interface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@ok php8
<?php

interface I {
const X = 1;
}

class C implements I {
const X = 2;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@kphp_should_fail php8
/C::X cannot override final constant I::X/
<?php

interface I {
final public const X = 1;
}

class C implements I {
const X = 2;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@ok php8
<?php

interface I {
final public const X = 1;
}

class C implements I {}
12 changes: 12 additions & 0 deletions tests/phpt/php8/final_constants/008_indirect_override.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@ok php8
<?php

interface I {
const X = 1;
}

class C implements I {}

class D extends C {
const X = 2;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@ok php8
<?php

interface I1 {
const C = 1;
}

interface I2 {
const C = 2;
}

class C implements I1, I2 {
const C = 3;
}
2 changes: 1 addition & 1 deletion tests/python/lib/file_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,5 @@ def search_php_bin(php8_require=False):
if sys.platform == "darwin":
return shutil.which("php")
if php8_require:
return shutil.which("php8.0")
return shutil.which("php8.1")
return shutil.which("php7.4")