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
73 changes: 48 additions & 25 deletions compiler/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,8 @@ VertexAdaptor<op_require> GenTree::get_require(bool once) {
}

template<Operation Op, Operation EmptyOp>
VertexAdaptor<Op> GenTree::get_func_call() {
VertexAdaptor<Op> GenTree::get_func_call(std::string name) {
auto location = auto_location();
string name{cur->str_val};
next_cur();

CE (expect(tok_oppar, "'('"));
skip_phpdoc_tokens();
Expand All @@ -243,6 +241,28 @@ VertexAdaptor<Op> GenTree::get_func_call() {
return call;
}

template<Operation Op, Operation EmptyOp>
VertexAdaptor<Op> GenTree::get_func_call() {
string name{cur->str_val};
next_cur();
return get_func_call<Op, EmptyOp>(name);
}

VertexAdaptor<op_func_call> GenTree::get_type_and_args_of_new_expression() {
if (test_expect(tok_class)) {
// here we met anon class definition and probably constructor's arguments
return get_class({}, ClassType::klass, true);
}

CE(!kphp_error(cur->type() == tok_func_name, "Expected class name after new"));
auto func_call = get_func_call<op_func_call, op_none>();
// hack to be more compatible with php
if (func_call->str_val == "Memcache") {
func_call->set_string("McMemcache");
}
return func_call;
}

VertexAdaptor<op_array> GenTree::get_short_array() {
auto location = auto_location();
next_cur();
Expand Down Expand Up @@ -526,13 +546,7 @@ VertexPtr GenTree::get_expr_top(bool was_arrow) {

case tok_new: {
next_cur();
CE(!kphp_error(cur->type() == tok_func_name, "Expected class name after new"));
auto func_call = get_func_call<op_func_call, op_none>();
// Hack to be more compatible with php
if (func_call->str_val == "Memcache") {
func_call->set_string("McMemcache");
}

auto func_call = get_type_and_args_of_new_expression();
res = gen_constructor_call_with_args(func_call->str_val, func_call->get_next()).set_location(func_call);
CE(res);
break;
Expand Down Expand Up @@ -1617,7 +1631,7 @@ void GenTree::parse_extends_implements() {
}
}

VertexPtr GenTree::get_class(vk::string_view phpdoc_str, ClassType class_type) {
VertexAdaptor<op_func_call> GenTree::get_class(vk::string_view phpdoc_str, ClassType class_type, bool is_anonymous) {
ClassModifiers modifiers;
if (test_expect(tok_abstract)) {
modifiers.set_abstract();
Expand All @@ -1633,20 +1647,26 @@ VertexPtr GenTree::get_class(vk::string_view phpdoc_str, ClassType class_type) {
CE(vk::any_of_equal(cur->type(), tok_class, tok_interface, tok_trait));
next_cur();

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

auto name_str = cur->str_val;
next_cur();
VertexAdaptor<op_func_call> res;
std::string class_name;
if (is_anonymous) {
class_name = gen_unique_name("anon_class", cur_function);
res = get_func_call<op_func_call, op_none>(class_name);
} else {
CE (!kphp_error(test_expect(tok_func_name), "Class name expected"));
class_name = std::string{cur->str_val.data(), cur->str_val.size()};
next_cur();
}

std::string full_class_name;
if (processing_file->namespace_name.empty() && phpdoc_tag_exists(phpdoc_str, php_doc_tag::kphp_tl_class)) {
kphp_assert(processing_file->is_builtin());
full_class_name = G->settings().tl_namespace_prefix.get() + name_str;
full_class_name = G->settings().tl_namespace_prefix.get() + class_name;
} else {
full_class_name = processing_file->namespace_name.empty() ? std::string{name_str} : processing_file->namespace_name + "\\" + name_str;
full_class_name = processing_file->namespace_name.empty() ? class_name : processing_file->namespace_name + "\\" + class_name;
}

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

StackPushPop<ClassPtr> c_alive(class_stack, cur_class, ClassPtr(new ClassData()));
Expand All @@ -1660,7 +1680,7 @@ VertexPtr GenTree::get_class(vk::string_view phpdoc_str, ClassType class_type) {
parse_extends_implements();

auto name_vertex = VertexAdaptor<op_func_name>::create().set_location(auto_location());
name_vertex->str_val = static_cast<std::string>(name_str);
name_vertex->str_val = class_name;

cur_class->file_id = processing_file;
cur_class->set_name_and_src_name(full_class_name, phpdoc_str); // with full namespaces and slashes
Expand All @@ -1686,7 +1706,7 @@ VertexPtr GenTree::get_class(vk::string_view phpdoc_str, ClassType class_type) {
G->register_and_require_function(cur_function, parsed_os, true); // push the class down the pipeline
}

return {};
return res;
}


Expand Down Expand Up @@ -2023,7 +2043,8 @@ VertexPtr GenTree::get_statement(vk::string_view phpdoc_str) {
if (cur_function->type == FunctionData::func_class_holder) {
return get_class_member(phpdoc_str);
} else if (vk::any_of_equal(cur->type(), tok_final, tok_abstract)) {
return get_class(phpdoc_str, ClassType::klass);
get_class(phpdoc_str, ClassType::klass);
return {};
}
next_cur();
kphp_error(0, "Unexpected access modifier outside of class body");
Expand Down Expand Up @@ -2153,13 +2174,15 @@ VertexPtr GenTree::get_statement(vk::string_view phpdoc_str) {
return empty;
}
case tok_class:
return get_class(phpdoc_str, ClassType::klass);
get_class(phpdoc_str, ClassType::klass);
return {};
case tok_interface:
return get_class(phpdoc_str, ClassType::interface);
get_class(phpdoc_str, ClassType::interface);
return {};
case tok_trait: {
auto res = get_class(phpdoc_str, ClassType::trait);
get_class(phpdoc_str, ClassType::trait);
kphp_error(processing_file->namespace_uses.empty(), "Usage of operator `use`(Aliasing/Importing) with traits is temporarily prohibited");
return res;
return {};
}
default: {
auto res = get_expression();
Expand Down
5 changes: 4 additions & 1 deletion compiler/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ class GenTree {
VertexAdaptor<Op> get_conv();
VertexAdaptor<op_require> get_require(bool once);
template<Operation Op, Operation EmptyOp>
VertexAdaptor<Op> get_func_call(std::string name);
template<Operation Op, Operation EmptyOp>
VertexAdaptor<Op> get_func_call();
VertexAdaptor<op_func_call> get_type_and_args_of_new_expression();
VertexAdaptor<op_array> get_short_array();
VertexAdaptor<op_string> get_string();
VertexAdaptor<op_string_build> get_string_build();
Expand Down Expand Up @@ -143,7 +146,7 @@ class GenTree {
static VertexAdaptor<op_func_call> gen_constructor_call_with_args(std::string allocated_class_name, std::vector<VertexPtr> args);
static VertexAdaptor<op_func_call> gen_constructor_call_with_args(ClassPtr allocated_class, std::vector<VertexPtr> args);

VertexPtr get_class(vk::string_view phpdoc_str, ClassType class_type);
VertexAdaptor<op_func_call> get_class(vk::string_view phpdoc_str, ClassType class_type, bool is_anonymous = false);
void parse_extends_implements();

static VertexPtr process_arrow(VertexPtr lhs, VertexPtr rhs);
Expand Down
3 changes: 2 additions & 1 deletion compiler/lexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,8 @@ void LexerData::hack_last_tokens() {
return;
}

if (are_last_tokens(tok_new, tok_func_name, except_token_tag<tok_oppar>{})) {
if (are_last_tokens(tok_new, tok_func_name, except_token_tag<tok_oppar>{}) ||
are_last_tokens(tok_new, tok_class, except_token_tag<tok_oppar>{})) {
Token t = tokens.back();
tokens.pop_back();
tokens.emplace_back(tok_oppar);
Expand Down
13 changes: 13 additions & 0 deletions tests/phpt/anon_classes/001_set_to_variable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@ok
<?php

function test_set_to_variable() {
$a = new class {
function say_foo() {
echo "foo";
}
};
$a->say_foo();
}

test_set_to_variable();
20 changes: 20 additions & 0 deletions tests/phpt/anon_classes/002_pass_to_function.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@ok
<?php

interface I {
function say_foo();
}

function call_say_foo(I $i) {
$i->say_foo();
}

function test_pass_to_function() {
call_say_foo(new class implements I {
function say_foo() {
echo "foo";
}
});
}

test_pass_to_function();
21 changes: 21 additions & 0 deletions tests/phpt/anon_classes/003_constructor_arguments.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@ok
<?php

function test_one_ctor_arg() {
new class(77) {
function __construct($num) {
echo $num;
}
};
}

function test_two_ctor_arg() {
new class("foo", "bar") {
function __construct($s1, $s2) {
echo $s1, $s2;
}
};
}

test_one_ctor_arg();
test_two_ctor_arg();
32 changes: 32 additions & 0 deletions tests/phpt/anon_classes/004_access_to_outer_class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
@ok
<?php

// example from https://www.php.net/manual/en/language.oop5.anonymous.php
class Outer {
private $prop = 1;
protected $prop2 = 2;

protected function func1() {
return 3;
}

public function func2() {
return new class($this->prop) extends Outer {
private $prop3;

public function __construct($prop) {
$this->prop3 = $prop;
}

public function func3() {
return $this->prop2 + $this->prop3 + $this->func1();
}
};
}
}

function test_access_to_outer_class() {
echo (new Outer)->func2()->func3();
}

test_access_to_outer_class();
24 changes: 24 additions & 0 deletions tests/phpt/anon_classes/005_extend_traits.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@ok
<?php

class Base {
public function sayHello() {
echo 'Hello ';
}
}

trait SayWorld {
public function sayHello() {
parent::sayHello();
echo 'World!';
}
}

function test_extend_traits() {
$obj = new class extends Base {
use SayWorld;
};
$obj->sayHello();
}

test_extend_traits();
17 changes: 17 additions & 0 deletions tests/phpt/anon_classes/006_anon_classes_equality.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@ok
<?php

function get_anon_class() {
return new class {};
}

function get_another_anon_class() {
return new class {};
}

function test_anon_classes_equality() {
echo (get_class(get_anon_class()) === get_class(get_anon_class()));
echo (get_class(get_anon_class()) === get_class(get_another_anon_class()));
}

test_anon_classes_equality();
11 changes: 11 additions & 0 deletions tests/phpt/anon_classes/007_constructor_type_mismatch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@kphp_should_fail
/pass int to argument \$num of anon_class.*::__construct\nbut it's declared as @param string/
<?php

function test_constructor_type_mismatch() {
new class(42) {
function __construct(string $num) {}
};
}

test_constructor_type_mismatch();
Copy link
Contributor Author

@drdzyk drdzyk Jul 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Full log of compiler error:

Compilation error at stage: Calc actual calls, gen by type-inferer.cpp:53
  007_constructor_type_mismatch.php:6  in test_constructor_type_mismatch
    new class(42) {

pass int to argument $num of anon_class\ue076317298712440_0::__construct
but it's declared as @param string

  007_constructor_type_mismatch.php:6  in test_constructor_type_mismatch
    new class(42) {
    42 is int


Compilation terminated due to errors