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/function-data.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ class FunctionData {
bool warn_unused_result = false;
bool is_flatten = false;
bool is_pure = false;
bool with_property_promotion = false;

function_palette::ColorContainer colors{}; // colors specified with @kphp-color
std::vector<FunctionPtr> *next_with_colors{nullptr}; // next colored functions reachable via call graph
Expand Down
60 changes: 55 additions & 5 deletions compiler/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ VertexPtr GenTree::get_expr_top(bool was_arrow) {
func_call->set_string("McMemcache");
}

res = gen_constructor_call_with_args(func_call->str_val, func_call->get_next()).set_location(func_call);
res = gen_constructor_call_with_args(func_call->str_val, func_call->as_vector()).set_location(func_call);
CE(res);
break;
}
Expand Down Expand Up @@ -872,9 +872,26 @@ VertexPtr GenTree::get_def_value() {
return val;
}

AccessModifiers GenTree::try_get_visibility_modifiers() {
if (test_expect(tok_public)) {
return AccessModifiers::public_;
} else if (test_expect(tok_private)) {
return AccessModifiers::private_;
} else if (test_expect(tok_protected)) {
return AccessModifiers::protected_;
}
return AccessModifiers::not_modifier_;
}

VertexAdaptor<op_func_param> GenTree::get_func_param() {
auto location = auto_location();

// possibly promoted property
const auto access_modifier = try_get_visibility_modifiers();
if (access_modifier != AccessModifiers::not_modifier_) {
next_cur();
}

const TypeHint *type_hint = get_typehint();
bool is_varg = false;

Expand All @@ -886,7 +903,12 @@ VertexAdaptor<op_func_param> GenTree::get_func_param() {
is_varg = true;
}
if (is_varg) {
kphp_error(!cur_function->has_variadic_param, "Function can not have ...$variadic more than once");
if (cur_function->has_variadic_param) {
kphp_error(0, "Function can not have ...$variadic more than once");
}
if (access_modifier != AccessModifiers::not_modifier_) {
kphp_error(0, "Cannot declare variadic promoted property");
}
cur_function->has_variadic_param = true;
}

Expand Down Expand Up @@ -920,6 +942,7 @@ VertexAdaptor<op_func_param> GenTree::get_func_param() {
v->type_hint = type_hint;
}
v->is_cast_param = is_cast_param;
v->access_modifier = access_modifier;

return v;
}
Expand Down Expand Up @@ -1052,7 +1075,7 @@ void GenTree::func_force_return(VertexAdaptor<op_function> func, VertexPtr val)
return_node = VertexAdaptor<op_return>::create();
}

vector<VertexPtr> next = cmd->get_next();
vector<VertexPtr> next = cmd->as_vector();
next.push_back(return_node);
func->cmd_ref() = VertexAdaptor<op_seq>::create(next);
}
Expand Down Expand Up @@ -1506,8 +1529,19 @@ VertexAdaptor<op_func_param_list> GenTree::parse_cur_function_param_list() {
CE(!kphp_error(ok_params_next, "Failed to parse function params"));
CE(expect(tok_clpar, "')'"));

for (size_t i = 1; i < params_next.size(); ++i) {
if (!params_next[i]->has_default_value()) {
for (size_t i = 0; i < params_next.size(); ++i) {
const auto &param = params_next[i];

// if promoted property outside constructor
if (cur_function->local_name() != ClassData::NAME_OF_CONSTRUCT && param->access_modifier != AccessModifiers::not_modifier_) {
kphp_error(0, "Cannot declare promoted property outside a constructor");
}

if (cur_function->local_name() == ClassData::NAME_OF_CONSTRUCT && cur_function->modifiers.is_abstract()) {
kphp_error(0, "Cannot declare promoted property in an abstract constructor");
}

if (i > 0 && !param->has_default_value()) {
kphp_error(!params_next[i - 1]->has_default_value(), "Optional parameter is provided before required");
}
}
Expand Down Expand Up @@ -1612,6 +1646,22 @@ VertexAdaptor<op_function> GenTree::get_function(TokenType tok, vk::string_view
cur_class->members.add_static_method(cur_function);
}

// add all the promoted properties in constructor to the class
if (cur_function->is_constructor()) {
for (const auto &p : cur_function->get_params()) {
const auto &param = p.as<op_func_param>();

if (param->access_modifier != AccessModifiers::not_modifier_) {
const auto field_modifiers = FieldModifiers{param->access_modifier};
auto var = VertexAdaptor<op_var>::create().set_location(auto_location());
var->str_val = param->var()->str_val;

cur_class->members.add_instance_field(var, {}, field_modifiers, "", param->type_hint);
cur_function->with_property_promotion = true;
}
}
}

// the function is ready, register it;
// the constructor is registered later, after the entire class is parsed
if (!cur_function->is_constructor()) {
Expand Down
1 change: 1 addition & 0 deletions compiler/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class GenTree {
VertexAdaptor<op_func_call> get_anonymous_function(TokenType tok = tok_function, bool is_static = false);
VertexAdaptor<op_function> get_function(TokenType tok, vk::string_view phpdoc_str, FunctionModifiers modifiers, std::vector<VertexAdaptor<op_func_param>> *uses_of_lambda = nullptr);

AccessModifiers try_get_visibility_modifiers();
ClassMemberModifiers parse_class_member_modifier_mask();
VertexPtr get_class_member(vk::string_view phpdoc_str);

Expand Down
44 changes: 44 additions & 0 deletions compiler/pipes/gen-tree-postprocess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -256,9 +256,53 @@ VertexPtr GenTreePostprocessPass::on_exit_vertex(VertexPtr root) {
return convert_array_with_spread_operators(array);
}

if (auto fun = root.try_as<op_function>()) {
if (fun->func_id->with_property_promotion) {
process_property_promotion(fun);
}
}

return root;
}

void GenTreePostprocessPass::process_property_promotion(VertexAdaptor<op_function> fun) const {
auto promoted_params = std::vector<VertexAdaptor<op_func_param>>();

const auto param_list = fun->param_list();
for (const auto &p : param_list->params()) {
const auto &param = p.try_as<op_func_param>();

if (param->access_modifier != AccessModifiers::not_modifier_) {
promoted_params.push_back(param);
}
}

auto func_stmts = fun->cmd()->as_vector();

// create an expression like "$this-><name> = <name>" for each promoted property
// all such expressions are placed at the very beginning of the expression list
// inside the constructor to avoid collisions with existing expressions
for (const auto &param : promoted_params) {
const auto &param_name = param->var()->str_val;
const auto *field = current_function->class_id->get_instance_field(param_name);
if (!field) {
continue;
}

const auto prop_var = field->var;
const auto this_vertex = ClassData::gen_vertex_this(fun->location);

auto prop_fetch = VertexAdaptor<op_instance_prop>::create(this_vertex).set_location(fun->location);
prop_fetch->set_string(param_name);
prop_fetch->var_id = prop_var;

const auto set_vertex = VertexAdaptor<op_set>::create(prop_fetch, param->var().clone());
func_stmts.insert(func_stmts.begin(), set_vertex);
}

fun->cmd_ref() = VertexAdaptor<op_seq>::create(func_stmts);
}

VertexAdaptor<op_array> array_vertex_from_slice(const VertexRange &args, size_t start, size_t end) {
return VertexAdaptor<op_array>::create(
std::vector<VertexPtr>{args.begin() + start, args.begin() + end}
Expand Down
1 change: 1 addition & 0 deletions compiler/pipes/gen-tree-postprocess.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ class GenTreePostprocessPass final : public FunctionPassBase {

// converts the spread operator (...$a) to a call to the array_merge_spread function
static VertexPtr convert_array_with_spread_operators(VertexAdaptor<op_array> array_vertex);
void process_property_promotion(VertexAdaptor<op_function> fun) const;
};
2 changes: 1 addition & 1 deletion compiler/pipes/preprocess-function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ class PreprocessFunctionPass final : public FunctionPassBase {
// just as ordinary positional argument:
// $this->fun(1, 2, 3) -> fun($this, 1, [2, 3])

const std::vector<VertexPtr> &cur_call_args = call->get_next();
const std::vector<VertexPtr> &cur_call_args = call->as_vector();
auto positional_args_start = cur_call_args.begin();

int min_args = func_args_n - 1; // variadic param may accept 0 args, so subtract 1
Expand Down
2 changes: 1 addition & 1 deletion compiler/pipes/register-variables.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#include "compiler/function-pass.h"

/**
* 1. Function parametres (with default values)
* 1. Function parameters (with default values)
* 2. Global variables
* 3. Static local variables (with default values)
* 4. Local variables
Expand Down
2 changes: 1 addition & 1 deletion compiler/pipes/resolve-self-static-parent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ VertexPtr ResolveSelfStaticParentPass::on_enter_vertex(VertexPtr v) {
this_vertex = VertexAdaptor<op_instance_prop>::create(this_vertex).set_location(v);
this_vertex->set_string(LambdaClassData::get_parent_this_name());
}
v = VertexAdaptor<op_func_call>::create(this_vertex, v->get_next()).set_location(v);
v = VertexAdaptor<op_func_call>::create(this_vertex, v->as_vector()).set_location(v);
v->extra_type = op_ex_func_call_arrow;
v->set_string(std::string{method->local_name()});
v.as<op_func_call>()->func_id = method;
Expand Down
6 changes: 5 additions & 1 deletion compiler/vertex-desc.json
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@
}
},
{
"comment": "var(); or var() = default_value(); type hints are stored in type_hint",
"comment": "[access_modifier] var() [= default_value()]; type hints are stored in type_hint",
"name": "op_func_param",
"base_name": "meta_op_base",
"sons": {
Expand Down Expand Up @@ -447,6 +447,10 @@
"is_callable": {
"type": "bool",
"default": "false"
},
"access_modifier": {
"type": "AccessModifiers",
"default": "AccessModifiers::not_modifier_"
}
}
},
Expand Down
10 changes: 9 additions & 1 deletion compiler/vertex-meta_op_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "common/wrappers/iterator_range.h"

#include "compiler/common.h"
#include "compiler/data/class-member-modifiers.h"
#include "compiler/data/data_ptr.h"
#include "compiler/data/vertex-adaptor.h"
#include "compiler/inferring/expr-node.h"
Expand Down Expand Up @@ -166,7 +167,14 @@ class vertex_inner<meta_op_base> {

VertexPtr &back() { return ith(size() - 1); }

std::vector<VertexPtr> get_next() { return std::vector<VertexPtr>(begin(), end()); }
/**
* as_vector returns a vector which is a set of VertexPtr that
* should be interpreted depending on the type of the vertex.
*
* For example, for 'op_seq' this will be equivalent to a list
* of vertices in the sequence.
*/
std::vector<VertexPtr> as_vector() { return {begin(), end()}; }

bool empty() { return size() == 0; }

Expand Down
10 changes: 10 additions & 0 deletions tests/phpt/php8/property_promotion/001_basic.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@kphp_should_fail php8
/assign string to Point::\$x/
<?php

class Point {
public function __construct(public int $x, public int $y, public int $z) {}
}

$point = new Point(1, 2, 3);
$point->x = "foo";
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@ok php8
<?php

class Test {
public string $prop2;

public function __construct(public string $prop1 = "", $param2 = "") {
$this->prop2 = $prop1 . $param2;
}
}

$test = new Test("hello ", "world");
echo $test->prop1 . "\n";
echo $test->prop2 . "\n";
20 changes: 20 additions & 0 deletions tests/phpt/php8/property_promotion/003_with_default_value.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@ok php8
<?php

class Point {
public function __construct(
public float $x = 0.0,
public float $y = 1.0,
public float $z = 2.0
) {}

public function print() {
echo $this->x . "\n";
echo $this->y . "\n";
echo $this->z . "\n";
}
}

(new Point(10.0))->print();
(new Point(10.0, 11.0))->print();
(new Point(10.0, 11.0, 12.0))->print();
9 changes: 9 additions & 0 deletions tests/phpt/php8/property_promotion/004_redeclarate_prop.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@kphp_should_fail php8
/Redeclaration of Test::\$prop/
<?php

class Test {
public $prop;

public function __construct(public $prop) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@kphp_should_fail php8
/Cannot declare promoted property in an abstract constructor/
<?php

abstract class Test {
abstract public function __construct(public int $x);
}
7 changes: 7 additions & 0 deletions tests/phpt/php8/property_promotion/006_static.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@kphp_should_fail php8
/Syntax error: missing varname after typehint/
<?php

class Test {
public function __construct(public static string $x) {}
}
Empty file.
7 changes: 7 additions & 0 deletions tests/phpt/php8/property_promotion/008_interface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@kphp_should_fail php8
/Cannot declare promoted property in an abstract constructor/
<?php

interface Test {
public function __construct(public int $x);
}
7 changes: 7 additions & 0 deletions tests/phpt/php8/property_promotion/009_not_in_constructor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@kphp_should_fail php8
/Cannot declare promoted property outside a constructor/
<?php

class Test {
public function foobar(public int $x, public int $y) {}
}
12 changes: 12 additions & 0 deletions tests/phpt/php8/property_promotion/010_trait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@ok php8
<?php

trait Test {
public function __construct(public $prop) {}
}

class Test2 {
use Test;
}

var_dump((new Test2(42))->prop);
7 changes: 7 additions & 0 deletions tests/phpt/php8/property_promotion/011_variadic.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@kphp_should_fail php8
/Cannot declare variadic promoted property/
<?php

class Test {
public function __construct(public string ...$strings) {}
}
11 changes: 11 additions & 0 deletions tests/phpt/php8/property_promotion/012_private.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@kphp_should_fail php8
/Can't access private field z/
<?php

class Point {
public function __construct(public int $x, public int $y, private int $z) {}
}

$point = new Point(1, 2, 3);
echo $point->x;
echo $point->z;