summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRĂ©mi Verschelde <remi@verschelde.fr>2022-04-27 13:11:24 +0200
committerGitHub <noreply@github.com>2022-04-27 13:11:24 +0200
commitf626e8ef91d9f0c97c29ca04f2b240c4eae5214e (patch)
tree5feac7724f750feec7a673828ed943b580a5fd28
parentadd51632246946f341c87e10c3ed3e8d660a6d94 (diff)
parent01d13ab2c16aa69a6f81c10dfe4845a50c3e0c63 (diff)
Merge pull request #60396 from vnen/gdscript-self-lambda
-rw-r--r--modules/gdscript/gdscript.h1
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp68
-rw-r--r--modules/gdscript/gdscript_analyzer.h3
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp4
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h2
-rw-r--r--modules/gdscript/gdscript_codegen.h2
-rw-r--r--modules/gdscript/gdscript_compiler.cpp2
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp19
-rw-r--r--modules/gdscript/gdscript_function.h1
-rw-r--r--modules/gdscript/gdscript_lambda_callable.cpp78
-rw-r--r--modules/gdscript/gdscript_lambda_callable.h25
-rw-r--r--modules/gdscript/gdscript_parser.cpp3
-rw-r--r--modules/gdscript/gdscript_parser.h2
-rw-r--r--modules/gdscript/gdscript_vm.cpp36
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/lambda_use_self.gd23
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/lambda_use_self.out5
16 files changed, 251 insertions, 23 deletions
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 79171cac73..caca7d8ca5 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -262,6 +262,7 @@ class GDScriptInstance : public ScriptInstance {
friend class GDScript;
friend class GDScriptFunction;
friend class GDScriptLambdaCallable;
+ friend class GDScriptLambdaSelfCallable;
friend class GDScriptCompiler;
friend struct GDScriptUtilityFunctionsDefinitions;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 05fb4a11c2..d346264933 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -2498,12 +2498,17 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
}
if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) {
- push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parser->current_function->identifier->name), p_call);
+ // Get the parent function above any lambda.
+ GDScriptParser::FunctionNode *parent_function = parser->current_function;
+ while (parent_function->source_lambda) {
+ parent_function = parent_function->source_lambda->parent_function;
+ }
+ push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parent_function->identifier->name), p_call);
} else if (!is_self && base_type.is_meta_type && !is_static) {
base_type.is_meta_type = false; // For `to_string()`.
push_error(vformat(R"*(Cannot call non-static function "%s()" on the class "%s" directly. Make an instance instead.)*", p_call->function_name, base_type.to_string()), p_call);
- } else if (is_self && !is_static && !lambda_stack.is_empty()) {
- push_error(vformat(R"*(Cannot call non-static function "%s()" from a lambda function.)*", p_call->function_name), p_call);
+ } else if (is_self && !is_static) {
+ mark_lambda_use_self();
}
call_type = return_type;
@@ -2636,10 +2641,10 @@ void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node)
if (!ClassDB::is_parent_class(parser->current_class->base_type.native_type, result.native_type)) {
push_error(R"*(Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node.)*", p_get_node);
- } else if (!lambda_stack.is_empty()) {
- push_error(R"*(Cannot use shorthand "get_node()" notation ("$") inside a lambda. Use a captured variable instead.)*", p_get_node);
}
+ mark_lambda_use_self();
+
p_get_node->set_datatype(result);
}
@@ -2854,21 +2859,25 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
MethodBind *getter = ClassDB::get_method(native, getter_name);
if (getter != nullptr) {
p_identifier->set_datatype(type_from_property(getter->get_return_info()));
+ p_identifier->source = GDScriptParser::IdentifierNode::INHERITED_VARIABLE;
}
return;
}
if (ClassDB::get_method_info(native, name, &method_info)) {
// Method is callable.
p_identifier->set_datatype(make_callable_type(method_info));
+ p_identifier->source = GDScriptParser::IdentifierNode::INHERITED_VARIABLE;
return;
}
if (ClassDB::get_signal(native, name, &method_info)) {
// Signal is a type too.
p_identifier->set_datatype(make_signal_type(method_info));
+ p_identifier->source = GDScriptParser::IdentifierNode::INHERITED_VARIABLE;
return;
}
if (ClassDB::has_enum(native, name)) {
p_identifier->set_datatype(make_native_enum_type(native, name));
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
return;
}
bool valid = false;
@@ -2877,6 +2886,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
p_identifier->is_constant = true;
p_identifier->reduced_value = int_constant;
p_identifier->set_datatype(type_from_variant(int_constant, p_identifier));
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
return;
}
}
@@ -2927,7 +2937,11 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
p_identifier->reduced_value = p_identifier->constant_source->initializer->reduced_value;
found_source = true;
break;
+ case GDScriptParser::IdentifierNode::INHERITED_VARIABLE:
+ mark_lambda_use_self();
+ break;
case GDScriptParser::IdentifierNode::MEMBER_VARIABLE:
+ mark_lambda_use_self();
p_identifier->variable_source->usages++;
[[fallthrough]];
case GDScriptParser::IdentifierNode::LOCAL_VARIABLE:
@@ -2958,18 +2972,37 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
}
if (found_source) {
- // If the identifier is local, check if it's any kind of capture by comparing their source function.
- // Only capture locals and members and enum values. Constants are still accessible from the lambda using the script reference.
- if (p_identifier->source == GDScriptParser::IdentifierNode::UNDEFINED_SOURCE || p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_CONSTANT || lambda_stack.is_empty()) {
- return;
+ if ((p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE) && parser->current_function && parser->current_function->is_static) {
+ // Get the parent function above any lambda.
+ GDScriptParser::FunctionNode *parent_function = parser->current_function;
+ while (parent_function->source_lambda) {
+ parent_function = parent_function->source_lambda->parent_function;
+ }
+ push_error(vformat(R"*(Cannot access instance variable "%s" from the static function "%s()".)*", p_identifier->name, parent_function->identifier->name), p_identifier);
}
- GDScriptParser::FunctionNode *function_test = lambda_stack.back()->get()->function;
- while (function_test != nullptr && function_test != p_identifier->source_function && function_test->source_lambda != nullptr && !function_test->source_lambda->captures_indices.has(p_identifier->name)) {
- function_test->source_lambda->captures_indices[p_identifier->name] = function_test->source_lambda->captures.size();
- function_test->source_lambda->captures.push_back(p_identifier);
- function_test = function_test->source_lambda->parent_function;
+ if (!lambda_stack.is_empty()) {
+ // If the identifier is a member variable (including the native class properties), we consider the lambda to be using `self`, so we keep a reference to the current instance.
+ if (p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE) {
+ mark_lambda_use_self();
+ return; // No need to capture.
+ }
+ // If the identifier is local, check if it's any kind of capture by comparing their source function.
+ // Only capture locals and enum values. Constants are still accessible from the lambda using the script reference. If not, this method is done.
+ if (p_identifier->source == GDScriptParser::IdentifierNode::UNDEFINED_SOURCE || p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_CONSTANT) {
+ return;
+ }
+
+ GDScriptParser::FunctionNode *function_test = lambda_stack.back()->get()->function;
+ // Make sure we aren't capturing variable in the same lambda.
+ // This also add captures for nested lambdas.
+ while (function_test != nullptr && function_test != p_identifier->source_function && function_test->source_lambda != nullptr && !function_test->source_lambda->captures_indices.has(p_identifier->name)) {
+ function_test->source_lambda->captures_indices[p_identifier->name] = function_test->source_lambda->captures.size();
+ function_test->source_lambda->captures.push_back(p_identifier);
+ function_test = function_test->source_lambda->parent_function;
+ }
}
+
return;
}
@@ -3149,6 +3182,7 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) {
void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) {
p_self->is_constant = false;
p_self->set_datatype(type_from_metatype(parser->current_class->get_datatype()));
+ mark_lambda_use_self();
}
void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscript) {
@@ -4141,6 +4175,12 @@ void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) {
#endif
}
+void GDScriptAnalyzer::mark_lambda_use_self() {
+ for (GDScriptParser::LambdaNode *lambda : lambda_stack) {
+ lambda->use_self = true;
+ }
+}
+
bool GDScriptAnalyzer::class_exists(const StringName &p_class) const {
return ClassDB::class_exists(p_class) && ClassDB::is_class_exposed(p_class);
}
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 7b8883e1d3..519e1975c4 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -42,7 +42,7 @@ class GDScriptAnalyzer {
HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
const GDScriptParser::EnumNode *current_enum = nullptr;
- List<const GDScriptParser::LambdaNode *> lambda_stack;
+ List<GDScriptParser::LambdaNode *> lambda_stack;
// Tests for detecting invalid overloading of script members
static _FORCE_INLINE_ bool has_member_name_conflict_in_script_class(const StringName &p_name, const GDScriptParser::ClassNode *p_current_class_node);
@@ -115,6 +115,7 @@ class GDScriptAnalyzer {
bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr);
void push_error(const String &p_message, const GDScriptParser::Node *p_origin);
void mark_node_unsafe(const GDScriptParser::Node *p_node);
+ void mark_lambda_use_self();
bool class_exists(const StringName &p_class) const;
Ref<GDScriptParserRef> get_parser_for(const String &p_path);
#ifdef DEBUG_ENABLED
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index a7e25f5aab..e72069bcd5 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -1211,8 +1211,8 @@ void GDScriptByteCodeGenerator::write_call_script_function(const Address &p_targ
append(p_function_name);
}
-void GDScriptByteCodeGenerator::write_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures) {
- append(GDScriptFunction::OPCODE_CREATE_LAMBDA, 1 + p_captures.size());
+void GDScriptByteCodeGenerator::write_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures, bool p_use_self) {
+ append(p_use_self ? GDScriptFunction::OPCODE_CREATE_SELF_LAMBDA : GDScriptFunction::OPCODE_CREATE_LAMBDA, 1 + p_captures.size());
for (int i = 0; i < p_captures.size(); i++) {
append(p_captures[i]);
}
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index 222ae13390..0503f66161 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -470,7 +470,7 @@ public:
virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
virtual void write_call_self_async(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
- virtual void write_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures) override;
+ virtual void write_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures, bool p_use_self) override;
virtual void write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) override;
virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) override;
virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) override;
diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h
index 3d42ce06c0..326b66a295 100644
--- a/modules/gdscript/gdscript_codegen.h
+++ b/modules/gdscript/gdscript_codegen.h
@@ -131,7 +131,7 @@ public:
virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
virtual void write_call_self_async(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
- virtual void write_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures) = 0;
+ virtual void write_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures, bool p_use_self) = 0;
virtual void write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) = 0;
virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) = 0;
virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) = 0;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 0138147fcc..225c2d0d45 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -1197,7 +1197,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
return GDScriptCodeGenerator::Address();
}
- gen->write_lambda(result, function, captures);
+ gen->write_lambda(result, function, captures, lambda->use_self);
for (int i = 0; i < captures.size(); i++) {
if (captures[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) {
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
index 74090d0aa9..dc114f2eff 100644
--- a/modules/gdscript/gdscript_disassembler.cpp
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -792,6 +792,25 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr = 3 + captures_count;
} break;
+ case OPCODE_CREATE_SELF_LAMBDA: {
+ int captures_count = _code_ptr[ip + 1 + instr_var_args];
+ GDScriptFunction *lambda = _lambdas_ptr[_code_ptr[ip + 2 + instr_var_args]];
+
+ text += DADDR(1 + captures_count);
+ text += "create self lambda from ";
+ text += lambda->name.operator String();
+ text += "function, captures (";
+
+ for (int i = 0; i < captures_count; i++) {
+ if (i > 0) {
+ text += ", ";
+ }
+ text += DADDR(1 + i);
+ }
+ text += ")";
+
+ incr = 3 + captures_count;
+ } break;
case OPCODE_JUMP: {
text += "jump ";
text += itos(_code_ptr[ip + 1]);
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index d07a167ca2..ba0d51c5cc 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -299,6 +299,7 @@ public:
OPCODE_AWAIT,
OPCODE_AWAIT_RESUME,
OPCODE_CREATE_LAMBDA,
+ OPCODE_CREATE_SELF_LAMBDA,
OPCODE_JUMP,
OPCODE_JUMP_IF,
OPCODE_JUMP_IF_NOT,
diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp
index baf93e1098..c43fa12c8c 100644
--- a/modules/gdscript/gdscript_lambda_callable.cpp
+++ b/modules/gdscript/gdscript_lambda_callable.cpp
@@ -93,3 +93,81 @@ GDScriptLambdaCallable::GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptF
h = (uint32_t)hash_djb2_one_64((uint64_t)this);
}
+
+bool GDScriptLambdaSelfCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
+ // Lambda callables are only compared by reference.
+ return p_a == p_b;
+}
+
+bool GDScriptLambdaSelfCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) {
+ // Lambda callables are only compared by reference.
+ return p_a < p_b;
+}
+
+uint32_t GDScriptLambdaSelfCallable::hash() const {
+ return h;
+}
+
+String GDScriptLambdaSelfCallable::get_as_text() const {
+ if (function->get_name() != StringName()) {
+ return function->get_name().operator String() + "(lambda)";
+ }
+ return "(anonymous lambda)";
+}
+
+CallableCustom::CompareEqualFunc GDScriptLambdaSelfCallable::get_compare_equal_func() const {
+ return compare_equal;
+}
+
+CallableCustom::CompareLessFunc GDScriptLambdaSelfCallable::get_compare_less_func() const {
+ return compare_less;
+}
+
+ObjectID GDScriptLambdaSelfCallable::get_object() const {
+ return object->get_instance_id();
+}
+
+void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
+#ifdef DEBUG_ENABLED
+ if (object->get_script_instance() == nullptr || object->get_script_instance()->get_language() != GDScriptLanguage::get_singleton()) {
+ ERR_PRINT("Trying to call a lambda with an invalid instance.");
+ r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
+ return;
+ }
+#endif
+
+ int captures_amount = captures.size();
+
+ if (captures_amount > 0) {
+ Vector<const Variant *> args;
+ args.resize(p_argcount + captures_amount);
+ for (int i = 0; i < captures_amount; i++) {
+ args.write[i] = &captures[i];
+ }
+ for (int i = 0; i < p_argcount; i++) {
+ args.write[i + captures_amount] = p_arguments[i];
+ }
+
+ r_return_value = function->call(static_cast<GDScriptInstance *>(object->get_script_instance()), args.ptrw(), args.size(), r_call_error);
+ r_call_error.argument -= captures_amount;
+ } else {
+ r_return_value = function->call(static_cast<GDScriptInstance *>(object->get_script_instance()), p_arguments, p_argcount, r_call_error);
+ }
+}
+
+GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) {
+ reference = p_self;
+ object = p_self.ptr();
+ function = p_function;
+ captures = p_captures;
+
+ h = (uint32_t)hash_djb2_one_64((uint64_t)this);
+}
+
+GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Object *p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) {
+ object = p_self;
+ function = p_function;
+ captures = p_captures;
+
+ h = (uint32_t)hash_djb2_one_64((uint64_t)this);
+}
diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h
index f6a54a1a2f..248176e32c 100644
--- a/modules/gdscript/gdscript_lambda_callable.h
+++ b/modules/gdscript/gdscript_lambda_callable.h
@@ -62,4 +62,29 @@ public:
virtual ~GDScriptLambdaCallable() = default;
};
+// Lambda callable that references a particular object, so it can use `self` in the body.
+class GDScriptLambdaSelfCallable : public CallableCustom {
+ GDScriptFunction *function = nullptr;
+ Ref<RefCounted> reference; // For objects that are RefCounted, keep a reference.
+ Object *object = nullptr; // For non RefCounted objects, use a direct pointer.
+ uint32_t h;
+
+ Vector<Variant> captures;
+
+ static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b);
+ static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b);
+
+public:
+ uint32_t hash() const override;
+ String get_as_text() const override;
+ CompareEqualFunc get_compare_equal_func() const override;
+ CompareLessFunc get_compare_less_func() const override;
+ ObjectID get_object() const override;
+ void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
+
+ GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures);
+ GDScriptLambdaSelfCallable(Object *p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures);
+ virtual ~GDScriptLambdaSelfCallable() = default;
+};
+
#endif // GDSCRIPT_LAMBDA_CALLABLE
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 67d778f932..67f6b61f14 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -2200,9 +2200,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_self(ExpressionNode *p_pre
if (current_function && current_function->is_static) {
push_error(R"(Cannot use "self" inside a static function.)");
}
- if (in_lambda) {
- push_error(R"(Cannot use "self" inside a lambda.)");
- }
SelfNode *self = alloc_node<SelfNode>();
self->current_class = current_class;
return self;
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 487b7d0449..10474db02f 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -767,6 +767,7 @@ public:
LOCAL_BIND, // Pattern bind.
MEMBER_VARIABLE,
MEMBER_CONSTANT,
+ INHERITED_VARIABLE,
};
Source source = UNDEFINED_SOURCE;
@@ -800,6 +801,7 @@ public:
FunctionNode *parent_function = nullptr;
Vector<IdentifierNode *> captures;
Map<StringName, int> captures_indices;
+ bool use_self = false;
bool has_name() const {
return function && function->identifier;
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index 2afb850c32..e28dd26c28 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -306,6 +306,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_AWAIT, \
&&OPCODE_AWAIT_RESUME, \
&&OPCODE_CREATE_LAMBDA, \
+ &&OPCODE_CREATE_SELF_LAMBDA, \
&&OPCODE_JUMP, \
&&OPCODE_JUMP_IF, \
&&OPCODE_JUMP_IF_NOT, \
@@ -2277,6 +2278,41 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
+ OPCODE(OPCODE_CREATE_SELF_LAMBDA) {
+ CHECK_SPACE(2 + instr_arg_count);
+
+ GD_ERR_BREAK(p_instance == nullptr);
+
+ ip += instr_arg_count;
+
+ int captures_count = _code_ptr[ip + 1];
+ GD_ERR_BREAK(captures_count < 0);
+
+ int lambda_index = _code_ptr[ip + 2];
+ GD_ERR_BREAK(lambda_index < 0 || lambda_index >= _lambdas_count);
+ GDScriptFunction *lambda = _lambdas_ptr[lambda_index];
+
+ Vector<Variant> captures;
+ captures.resize(captures_count);
+ for (int i = 0; i < captures_count; i++) {
+ GET_INSTRUCTION_ARG(arg, i);
+ captures.write[i] = *arg;
+ }
+
+ GDScriptLambdaSelfCallable *callable;
+ if (Object::cast_to<RefCounted>(p_instance->owner)) {
+ callable = memnew(GDScriptLambdaSelfCallable(Ref<RefCounted>(Object::cast_to<RefCounted>(p_instance->owner)), lambda, captures));
+ } else {
+ callable = memnew(GDScriptLambdaSelfCallable(p_instance->owner, lambda, captures));
+ }
+
+ GET_INSTRUCTION_ARG(result, captures_count);
+ *result = Callable(callable);
+
+ ip += 3;
+ }
+ DISPATCH_OPCODE;
+
OPCODE(OPCODE_JUMP) {
CHECK_SPACE(2);
int to = _code_ptr[ip + 1];
diff --git a/modules/gdscript/tests/scripts/runtime/features/lambda_use_self.gd b/modules/gdscript/tests/scripts/runtime/features/lambda_use_self.gd
new file mode 100644
index 0000000000..3d063869ab
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/lambda_use_self.gd
@@ -0,0 +1,23 @@
+var member = "foo"
+
+func bar():
+ print("bar")
+
+func test():
+ var lambda1 = func():
+ print(member)
+ lambda1.call()
+
+ var lambda2 = func():
+ var nested = func():
+ print(member)
+ nested.call()
+ lambda2.call()
+
+ var lambda3 = func():
+ bar()
+ lambda3.call()
+
+ var lambda4 = func():
+ return self
+ print(lambda4.call() == self)
diff --git a/modules/gdscript/tests/scripts/runtime/features/lambda_use_self.out b/modules/gdscript/tests/scripts/runtime/features/lambda_use_self.out
new file mode 100644
index 0000000000..53d602b234
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/lambda_use_self.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+foo
+foo
+bar
+true