Skip to content
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
1 change: 1 addition & 0 deletions compiler/data/class-member-modifiers.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ enum class AbstractModifiers : uint8_t {
not_modifier_,
final_,
abstract_,
readonly_,
};

enum class AccessModifiers : uint8_t {
Expand Down
4 changes: 3 additions & 1 deletion compiler/data/class-members.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ ClassMemberInstanceField::ClassMemberInstanceField(ClassPtr klass, VertexAdaptor
root->var_id = var;
var->init_val = def_val;
var->class_id = klass;
var->marked_as_const = klass->is_immutable || phpdoc_tag_exists(phpdoc_str, php_doc_tag::kphp_const);
var->marked_as_const = klass->is_immutable ||
modifiers.is_readonly() ||
phpdoc_tag_exists(phpdoc_str, php_doc_tag::kphp_const);
}

const TypeData *ClassMemberInstanceField::get_inferred_type() const {
Expand Down
50 changes: 40 additions & 10 deletions compiler/data/field-modifiers.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,45 +12,75 @@
class FieldModifiers {
public:
FieldModifiers() = default;

explicit FieldModifiers(AccessModifiers access_modifiers) :
access_modifier_(access_modifiers) {
}

FieldModifiers(AccessModifiers access_modifiers, AbstractModifiers abstract_modifiers) :
abstract_modifier_(abstract_modifiers), access_modifier_(access_modifiers) {
}

FieldModifiers &set_public() {
kphp_error(!is_private() && !is_protected(), "Mupliple access modifiers (e.g. public and private at the same time) are not allowed");
kphp_error(!is_private() && !is_protected(), "Multiple access modifiers (e.g. public and private at the same time) are not allowed");
access_modifier_ = AccessModifiers::public_;
return *this;
}

FieldModifiers &set_private() {
kphp_error(!is_public() && !is_protected(), "Mupliple access modifiers (e.g. public and private at the same time) are not allowed");
kphp_error(!is_public() && !is_protected(), "Multiple access modifiers (e.g. public and private at the same time) are not allowed");
access_modifier_ = AccessModifiers::private_;
return *this;
}

FieldModifiers &set_protected() {
kphp_error(!is_private() && !is_public(), "Mupliple access modifiers (e.g. public and private at the same time) are not allowed");
kphp_error(!is_private() && !is_public(), "Multiple access modifiers (e.g. public and private at the same time) are not allowed");
access_modifier_ = AccessModifiers::protected_;
return *this;
}

bool is_public() const { return access_modifier_ == AccessModifiers::public_; }
bool is_private() const { return access_modifier_ == AccessModifiers::private_; }
bool is_protected() const { return access_modifier_ == AccessModifiers::protected_; }
FieldModifiers &set_readonly() {
abstract_modifier_ = AbstractModifiers::readonly_;
return *this;
}

bool is_public() const { return access_modifier_ == AccessModifiers::public_; }
bool is_private() const { return access_modifier_ == AccessModifiers::private_; }
bool is_protected() const { return access_modifier_ == AccessModifiers::protected_; }
bool is_readonly() const { return abstract_modifier_ == AbstractModifiers::readonly_; }

std::string to_string() const {
std::string res;
switch (access_modifier_) {
case AccessModifiers::public_ : return "public";
case AccessModifiers::private_ : return "private";
case AccessModifiers::protected_: return "protected";
default: return "";
case AccessModifiers::public_:
res = "public";
break;
case AccessModifiers::private_ :
res = "private";
break;
case AccessModifiers::protected_:
res = "protected";
break;
default:
break;
}

if (is_readonly()) {
if (res.empty()) {
res += "readonly";
} else {
res += " readonly";
}
}

return res;
}

friend bool operator==(const FieldModifiers &lhs, const FieldModifiers &rhs) {
return lhs.access_modifier_ == rhs.access_modifier_;
}

private:
AbstractModifiers abstract_modifier_{AbstractModifiers ::not_modifier_};
AccessModifiers access_modifier_{AccessModifiers ::not_modifier_};
};
21 changes: 16 additions & 5 deletions compiler/data/function-modifiers.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,17 @@ class FunctionModifiers {
}

void set_public() {
kphp_error(!is_private() && !is_protected(), "Mupliple access modifiers (e.g. public and private at the same time) are not allowed");
kphp_error(!is_private() && !is_protected(), "Multiply access modifiers (e.g. public and private at the same time) are not allowed");
access_modifier_ = AccessModifiers::public_;
}

void set_private() {
kphp_error(!is_public() && !is_protected(), "Mupliple access modifiers (e.g. public and private at the same time) are not allowed");
kphp_error(!is_public() && !is_protected(), "Multiply access modifiers (e.g. public and private at the same time) are not allowed");
access_modifier_ = AccessModifiers::private_;
}

void set_protected() {
kphp_error(!is_private() && !is_public(), "Mupliple access modifiers (e.g. public and private at the same time) are not allowed");
kphp_error(!is_private() && !is_public(), "Multiply access modifiers (e.g. public and private at the same time) are not allowed");
access_modifier_ = AccessModifiers::protected_;
}

Expand All @@ -52,6 +52,11 @@ class FunctionModifiers {
abstract_modifier_ = AbstractModifiers::abstract_;
}

void set_readonly() {
kphp_error(!is_readonly(), "Several readonly modifiers are not allowed");
abstract_modifier_ = AbstractModifiers::readonly_;
}

bool is_static() const { return scope_modifier_ == ScopeModifiers::static_; }
bool is_instance() const { return scope_modifier_ == ScopeModifiers::instance_; }
bool is_nonmember() const { return !is_static() && !is_instance(); }
Expand All @@ -60,8 +65,9 @@ class FunctionModifiers {
bool is_private() const { return access_modifier_ == AccessModifiers::private_; }
bool is_protected() const { return access_modifier_ == AccessModifiers::protected_; }

bool is_final() const { return abstract_modifier_ == AbstractModifiers::final_; }
bool is_abstract() const { return abstract_modifier_ == AbstractModifiers::abstract_; }
bool is_final() const { return abstract_modifier_ == AbstractModifiers::final_; }
bool is_abstract() const { return abstract_modifier_ == AbstractModifiers::abstract_; }
bool is_readonly() const { return abstract_modifier_ == AbstractModifiers::readonly_; }

std::string to_string() const {
std::string res;
Expand All @@ -71,6 +77,7 @@ class FunctionModifiers {
if (is_public()) res += " public";
if (is_private()) res += " private";
if (is_protected()) res += " protected";
if (is_readonly()) res += " readonly";

if (is_static()) res += " static";
if (is_instance()) res += " instance";
Expand All @@ -83,6 +90,10 @@ class FunctionModifiers {
return access_modifier_;
}

AbstractModifiers abstract_modifiers() const {
return abstract_modifier_;
}

static FunctionModifiers instance_public() {
FunctionModifiers res;
res.set_instance();
Expand Down
1 change: 1 addition & 0 deletions compiler/debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ std::string debugTokenName(TokenType t) {
{tok_static, "tok_static"},
{tok_final, "tok_final"},
{tok_abstract, "tok_abstract"},
{tok_readonly, "tok_readonly"},
{tok_goto, "tok_goto"},
{tok_isset, "tok_isset"},
{tok_declare, "tok_declare"},
Expand Down
8 changes: 6 additions & 2 deletions compiler/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1438,6 +1438,9 @@ ClassMemberModifiers GenTree::parse_class_member_modifier_mask() {
case tok_abstract:
modifiers.set_abstract();
break;
case tok_readonly:
modifiers.set_readonly();
break;
default:
return modifiers;
}
Expand Down Expand Up @@ -1482,9 +1485,9 @@ VertexPtr GenTree::get_class_member(vk::string_view phpdoc_str) {
kphp_error(!cur_class->is_interface(), "Interfaces may not include member variables");
kphp_error(!modifiers.is_final() && !modifiers.is_abstract(), "Class fields can not be declared final/abstract");
if (modifiers.is_static()) {
return get_static_field_list(phpdoc_str, FieldModifiers{modifiers.access_modifier()}, type_hint);
return get_static_field_list(phpdoc_str, FieldModifiers{modifiers.access_modifier(), modifiers.abstract_modifiers()}, type_hint);
}
get_instance_var_list(phpdoc_str, FieldModifiers{modifiers.access_modifier()}, type_hint);
get_instance_var_list(phpdoc_str, FieldModifiers{modifiers.access_modifier(), modifiers.abstract_modifiers()}, type_hint);
return {};
}

Expand Down Expand Up @@ -2078,6 +2081,7 @@ VertexPtr GenTree::get_statement(vk::string_view phpdoc_str) {
return get_const_after_explicit_access_modifier(access);
}
// fall through
case tok_readonly:
case tok_final:
case tok_abstract:
if (cur_function->type == FunctionData::func_class_holder) {
Expand Down
1 change: 1 addition & 0 deletions compiler/keywords.gperf
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ global, tok_global
static, tok_static
final, tok_final
abstract, tok_abstract
readonly, tok_readonly
goto, tok_goto
return, tok_return
list, tok_list
Expand Down
15 changes: 15 additions & 0 deletions compiler/pipes/sort-and-inherit-classes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,21 @@ void SortAndInheritClassesF::check_on_finish(DataStream<FunctionPtr> &os) {
kphp_error(!c->parent_class->get_instance_method(m.local_name()),
fmt_format("Cannot make non static method `{}` static", m.function->get_human_readable_name()));
});

c->members.for_each([&c](const ClassMemberInstanceField &m) {
const auto *parent_field = c->parent_class->get_instance_field(m.local_name());
if (parent_field) {
const auto is_readonly = m.modifiers.is_readonly();
const auto parent_is_readonly = parent_field->modifiers.is_readonly();

if (is_readonly != parent_is_readonly) {
const char *current = is_readonly ? "readonly" : "writeable";
const char *parent = parent_is_readonly ? "readonly" : "writeable";

kphp_error(0, fmt_format("Cannot redeclare {} {}::{} as {} {}::{}", parent, c->parent_class->get_name(), parent_field->local_name(), current, c->get_name(), m.local_name()));
}
}
});
}

// For stable code generation of polymorphic classes body
Expand Down
1 change: 1 addition & 0 deletions compiler/token.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ enum TokenType {
tok_static,
tok_final,
tok_abstract,
tok_readonly,
tok_goto,
tok_isset,
tok_declare,
Expand Down
4 changes: 4 additions & 0 deletions tests/cpp/compiler/lexer-test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ 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(;)"}},

// readonly modifier
{"public readonly string $prop;", {"tok_public(public)", "tok_readonly(readonly)", "tok_string(string)", "tok_var_name($prop)", "tok_semicolon(;)"}},
{"readonly $prop = 100;", {"tok_readonly(readonly)", "tok_var_name($prop)", "tok_eq1(=)", "tok_int_const(100)", "tok_semicolon(;)"}},
};

for (const auto &test : tests) {
Expand Down
16 changes: 16 additions & 0 deletions tests/phpt/php8/readonly_modifier/001_readonly_property.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@kphp_should_fail
/Modification of const/
<?php

class Foo {
public readonly string $prop_readonly = "hello";
public string $prop_default = "hello";

public function __construct() {
$this->prop = "world"; // ok
}
}

$foo = new Foo();
$foo->prop_readonly = "world"; // error
$foo->prop_default = "world"; // ok
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@kphp_should_fail
/You may not override field: `prop_readonly`, in class: `Boo`/
/Cannot redeclare readonly Foo::prop_readonly as writeable Boo::prop_readonly/
/Cannot redeclare writeable Goo::prop_readonly as readonly Doo::prop_readonly/
<?php

class Foo {
public readonly string $prop_readonly = "hello";
}

class Boo extends Foo {
public string $prop_readonly = "hello";
}

class Goo {
public string $prop_readonly = "hello";
}

class Doo extends Goo {
public readonly string $prop_readonly = "hello";
}
11 changes: 11 additions & 0 deletions tests/phpt/php8/readonly_modifier/003_several_readonly.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@kphp_should_fail
/Several readonly modifiers are not allowed/
/Several readonly modifiers are not allowed/
/Several readonly modifiers are not allowed/
<?php

class Foo {
public readonly readonly string $prop_readonly = "hello";
public readonly readonly $prop_readonly1 = "hello";
readonly readonly $prop_readonly1 = "hello";
}