diff --git a/compiler/pipes/check-classes.cpp b/compiler/pipes/check-classes.cpp index 8c788019ae..75e5765129 100644 --- a/compiler/pipes/check-classes.cpp +++ b/compiler/pipes/check-classes.cpp @@ -72,7 +72,6 @@ inline void CheckClassesPass::check_static_fields_inited(ClassPtr klass) { } inline void CheckClassesPass::check_instance_fields_inited(ClassPtr klass) { - // TODO KPHP-221: the old code is kept for now (check for Unknown) klass->members.for_each([](const ClassMemberInstanceField &f) { PrimitiveType ptype = f.var->tinf_node.get_type()->get_real_ptype(); kphp_error(ptype != tp_any, diff --git a/compiler/pipes/filter-only-actually-used.cpp b/compiler/pipes/filter-only-actually-used.cpp index 4c77e8eaf8..8cfbdf905d 100644 --- a/compiler/pipes/filter-only-actually-used.cpp +++ b/compiler/pipes/filter-only-actually-used.cpp @@ -9,6 +9,78 @@ #include "compiler/data/src-file.h" #include "compiler/compiler-core.h" #include "compiler/threading/profiler.h" +#include "compiler/vertex-util.h" + +// having a typed callable __invoke(), which is a virtual function with switch-case dispatching, +// replace body of `case {lambda_class_to_remove.hash()}:` (which is a lambda invoke call) +// to just `break`; see below, why this is important +class RemoveLambdaCallFromTypedCallablePass final : public FunctionPassBase { + std::string case_hash; + +public: + std::string get_description() override { + return "Remove lambda call from typed callable"; + } + + explicit RemoveLambdaCallFromTypedCallablePass(ClassPtr lambda_class_to_remove) + : case_hash(std::to_string(lambda_class_to_remove->get_hash())) {} + + VertexPtr on_enter_vertex(VertexPtr root) override { + if (auto as_case = root.try_as()) { + if (auto as_int_const = as_case->expr().try_as()) { + if (as_int_const->str_val == case_hash) { + auto level1 = VertexUtil::create_int_const(1); + return VertexAdaptor::create(as_int_const, VertexAdaptor::create(VertexAdaptor::create(level1))); + } + } + } + + return root; + } +}; + +// when a lambda with `use` statement (=> with an instance field) occurs inside unused function, +// it still can be reachable from a typed callable __invoke(), +// but its field types can't be inferred, they'll be left 'any' and trigger an error after +// to prevent this, manually remove this lambda from that __invoke() body +// as well as remove all lambda's mentions from used_functions +class AnalyzeLambdasInUnusedFunctionPass final : public FunctionPassBase { + IdMap &used_functions; + +public: + std::string get_description() override { + return "Analyze lambdas in unused function"; + } + + explicit AnalyzeLambdasInUnusedFunctionPass(IdMap &used_functions) + : used_functions(used_functions) {} + + VertexPtr on_enter_vertex(VertexPtr root) override { + if (auto as_call = root.try_as(); + as_call && as_call->func_id->class_id && as_call->func_id->class_id->is_lambda_class() && as_call->func_id->is_constructor()) { + ClassPtr lambda_class = as_call->func_id->class_id; + const ClassMemberInstanceMethod *m_invoke = lambda_class->members.get_instance_method("__invoke"); + if (m_invoke && used_functions[m_invoke->function->outer_function]) { + // f_lambda occurs inside unused function, but is used; the only reason is it's used from a typed callable + FunctionPtr f_lambda = m_invoke->function->outer_function; + kphp_assert(lambda_class->implements.size() == 1 && lambda_class->implements[0]->is_typed_callable_interface()); + FunctionPtr f_typed_invoke = lambda_class->implements[0]->members.get_instance_method("__invoke")->function; + + RemoveLambdaCallFromTypedCallablePass pass(lambda_class); + run_function_pass(f_typed_invoke->root, &pass); + + AnalyzeLambdasInUnusedFunctionPass self_pass(used_functions); + run_function_pass(f_lambda, &self_pass); + + used_functions[f_lambda] = {}; + used_functions[m_invoke->function] = {}; + used_functions[lambda_class->construct_function] = {}; + } + } + + return root; + } +}; namespace { @@ -261,6 +333,16 @@ void FilterOnlyActuallyUsedFunctionsF::on_finish(DataStream &os) { remove_unused_class_methods(all, used_functions); stage::die_if_global_errors(); + // remove lambdas from unused functions, see comments above + for (const auto &f_and_e : all) { + FunctionPtr fun = f_and_e.first; + if (fun->has_lambdas_inside && !fun->is_lambda() && !used_functions[fun]) { + AnalyzeLambdasInUnusedFunctionPass pass(used_functions); + run_function_pass(fun, &pass); + } + } + stage::die_if_global_errors(); + // forward the reachable functions into the data stream; // this should be the last step for (const auto &f : used_functions) { diff --git a/compiler/pipes/generate-virtual-methods.cpp b/compiler/pipes/generate-virtual-methods.cpp index 681b564473..5bb1e2b4ce 100644 --- a/compiler/pipes/generate-virtual-methods.cpp +++ b/compiler/pipes/generate-virtual-methods.cpp @@ -422,10 +422,6 @@ void generate_body_of_virtual_method(FunctionPtr virtual_function) { cases.emplace_back(v_case); } } - if (!cases.empty()) { - auto case_default_warn = generate_critical_error_call(fmt_format("call method({}) on null object", virtual_function->as_human_readable(false))); - cases.emplace_back(VertexAdaptor::create(VertexAdaptor::create(case_default_warn))); - } if (cases.empty() && !stage::has_error()) { // when there are no inheritors of an interface, generate an empty body if possible — @@ -436,7 +432,7 @@ void generate_body_of_virtual_method(FunctionPtr virtual_function) { auto call_get_hash = VertexAdaptor::create(ClassData::gen_vertex_this({})); call_get_hash->str_val = "get_hash_of_class"; call_get_hash->func_id = G->get_function(call_get_hash->str_val); - virtual_function->root->cmd_ref() = VertexAdaptor::create(VertexUtil::create_switch_vertex(virtual_function, call_get_hash, std::move(cases))); + virtual_function->root->cmd_ref() = VertexAdaptor::create(VertexUtil::create_switch_vertex(virtual_function, call_get_hash, std::move(cases)), generate_critical_error_call(fmt_format("call method({}) on null object", virtual_function->as_human_readable(false)))); } virtual_function->type = FunctionData::func_local; // could be func_extern before, but now it has a body diff --git a/tests/phpt/lambdas/020_uses_in_lambda.php b/tests/phpt/lambdas/020_uses_in_lambda.php index cb185054a3..4dff4c2f53 100644 --- a/tests/phpt/lambdas/020_uses_in_lambda.php +++ b/tests/phpt/lambdas/020_uses_in_lambda.php @@ -110,3 +110,64 @@ function l2_modif() { } (new WithFCapturingDeep)->l2(); (new WithFCapturingDeep)->l2_modif(); + +class Example020 { + static private function takeInt(int $i) { echo $i; } + + public function unused_function(): void { + $tmp_var = ""; + $this->call_function(function() use ($tmp_var): void {}); + $int = 10; + $this->call_function(function() use ($int): void { self::takeInt($int); }); + } + + /** @param callable():void $fn */ + public function call_function(callable $fn): void { + $fn(); + } +} + +(new Example020())->call_function(function(): void {}); + +class Bxample020 { + /** + * @param callable(int):int $fn + */ + public function unused_function(callable $fn): int { + return $this->test2(function () use ($fn) { + return $fn(1); + }); + } + + /** + * @param callable():int $fn + */ + public function test2(callable $fn): int { + return $fn(); + } +} + +$bxample = new Bxample020(); +$bxample->test2(function() { return 0; }); + + +class Cxample { + public function unused_function(): void { + $tmp_var = ""; + $this->call_function_first(function() use ($tmp_var): void { + $this->call_function_second(function() use ($tmp_var) {}); + }); + } + + /** @param callable():void $fn */ + public function call_function_first(callable $fn): void { + $fn(); + } + + /** @param callable():void $fn */ + public function call_function_second(callable $fn): void { + $fn(); + } +} + +(new Cxample)->call_function_first(function():void{} );