GDScript: Allow using self in lambdas
This commit is contained in:
parent
690fefe43e
commit
01d13ab2c1
16 changed files with 251 additions and 23 deletions
|
@ -262,6 +262,7 @@ class GDScriptInstance : public ScriptInstance {
|
||||||
friend class GDScript;
|
friend class GDScript;
|
||||||
friend class GDScriptFunction;
|
friend class GDScriptFunction;
|
||||||
friend class GDScriptLambdaCallable;
|
friend class GDScriptLambdaCallable;
|
||||||
|
friend class GDScriptLambdaSelfCallable;
|
||||||
friend class GDScriptCompiler;
|
friend class GDScriptCompiler;
|
||||||
friend struct GDScriptUtilityFunctionsDefinitions;
|
friend struct GDScriptUtilityFunctionsDefinitions;
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
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) {
|
} else if (!is_self && base_type.is_meta_type && !is_static) {
|
||||||
base_type.is_meta_type = false; // For `to_string()`.
|
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);
|
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()) {
|
} else if (is_self && !is_static) {
|
||||||
push_error(vformat(R"*(Cannot call non-static function "%s()" from a lambda function.)*", p_call->function_name), p_call);
|
mark_lambda_use_self();
|
||||||
}
|
}
|
||||||
|
|
||||||
call_type = return_type;
|
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)) {
|
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);
|
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);
|
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);
|
MethodBind *getter = ClassDB::get_method(native, getter_name);
|
||||||
if (getter != nullptr) {
|
if (getter != nullptr) {
|
||||||
p_identifier->set_datatype(type_from_property(getter->get_return_info()));
|
p_identifier->set_datatype(type_from_property(getter->get_return_info()));
|
||||||
|
p_identifier->source = GDScriptParser::IdentifierNode::INHERITED_VARIABLE;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ClassDB::get_method_info(native, name, &method_info)) {
|
if (ClassDB::get_method_info(native, name, &method_info)) {
|
||||||
// Method is callable.
|
// Method is callable.
|
||||||
p_identifier->set_datatype(make_callable_type(method_info));
|
p_identifier->set_datatype(make_callable_type(method_info));
|
||||||
|
p_identifier->source = GDScriptParser::IdentifierNode::INHERITED_VARIABLE;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ClassDB::get_signal(native, name, &method_info)) {
|
if (ClassDB::get_signal(native, name, &method_info)) {
|
||||||
// Signal is a type too.
|
// Signal is a type too.
|
||||||
p_identifier->set_datatype(make_signal_type(method_info));
|
p_identifier->set_datatype(make_signal_type(method_info));
|
||||||
|
p_identifier->source = GDScriptParser::IdentifierNode::INHERITED_VARIABLE;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ClassDB::has_enum(native, name)) {
|
if (ClassDB::has_enum(native, name)) {
|
||||||
p_identifier->set_datatype(make_native_enum_type(native, name));
|
p_identifier->set_datatype(make_native_enum_type(native, name));
|
||||||
|
p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bool valid = false;
|
bool valid = false;
|
||||||
|
@ -2877,6 +2886,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
|
||||||
p_identifier->is_constant = true;
|
p_identifier->is_constant = true;
|
||||||
p_identifier->reduced_value = int_constant;
|
p_identifier->reduced_value = int_constant;
|
||||||
p_identifier->set_datatype(type_from_variant(int_constant, p_identifier));
|
p_identifier->set_datatype(type_from_variant(int_constant, p_identifier));
|
||||||
|
p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2927,7 +2937,11 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
|
||||||
p_identifier->reduced_value = p_identifier->constant_source->initializer->reduced_value;
|
p_identifier->reduced_value = p_identifier->constant_source->initializer->reduced_value;
|
||||||
found_source = true;
|
found_source = true;
|
||||||
break;
|
break;
|
||||||
|
case GDScriptParser::IdentifierNode::INHERITED_VARIABLE:
|
||||||
|
mark_lambda_use_self();
|
||||||
|
break;
|
||||||
case GDScriptParser::IdentifierNode::MEMBER_VARIABLE:
|
case GDScriptParser::IdentifierNode::MEMBER_VARIABLE:
|
||||||
|
mark_lambda_use_self();
|
||||||
p_identifier->variable_source->usages++;
|
p_identifier->variable_source->usages++;
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case GDScriptParser::IdentifierNode::LOCAL_VARIABLE:
|
case GDScriptParser::IdentifierNode::LOCAL_VARIABLE:
|
||||||
|
@ -2958,18 +2972,37 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
|
||||||
}
|
}
|
||||||
|
|
||||||
if (found_source) {
|
if (found_source) {
|
||||||
// If the identifier is local, check if it's any kind of capture by comparing their source function.
|
if ((p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE) && parser->current_function && parser->current_function->is_static) {
|
||||||
// Only capture locals and members and enum values. Constants are still accessible from the lambda using the script reference.
|
// Get the parent function above any lambda.
|
||||||
if (p_identifier->source == GDScriptParser::IdentifierNode::UNDEFINED_SOURCE || p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_CONSTANT || lambda_stack.is_empty()) {
|
GDScriptParser::FunctionNode *parent_function = parser->current_function;
|
||||||
return;
|
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;
|
if (!lambda_stack.is_empty()) {
|
||||||
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)) {
|
// 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.
|
||||||
function_test->source_lambda->captures_indices[p_identifier->name] = function_test->source_lambda->captures.size();
|
if (p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE) {
|
||||||
function_test->source_lambda->captures.push_back(p_identifier);
|
mark_lambda_use_self();
|
||||||
function_test = function_test->source_lambda->parent_function;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3149,6 +3182,7 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) {
|
||||||
void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) {
|
void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) {
|
||||||
p_self->is_constant = false;
|
p_self->is_constant = false;
|
||||||
p_self->set_datatype(type_from_metatype(parser->current_class->get_datatype()));
|
p_self->set_datatype(type_from_metatype(parser->current_class->get_datatype()));
|
||||||
|
mark_lambda_use_self();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscript) {
|
void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscript) {
|
||||||
|
@ -4121,6 +4155,12 @@ void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) {
|
||||||
#endif
|
#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 {
|
bool GDScriptAnalyzer::class_exists(const StringName &p_class) const {
|
||||||
return ClassDB::class_exists(p_class) && ClassDB::is_class_exposed(p_class);
|
return ClassDB::class_exists(p_class) && ClassDB::is_class_exposed(p_class);
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ class GDScriptAnalyzer {
|
||||||
HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
|
HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
|
||||||
|
|
||||||
const GDScriptParser::EnumNode *current_enum = nullptr;
|
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
|
// 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);
|
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);
|
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 push_error(const String &p_message, const GDScriptParser::Node *p_origin);
|
||||||
void mark_node_unsafe(const GDScriptParser::Node *p_node);
|
void mark_node_unsafe(const GDScriptParser::Node *p_node);
|
||||||
|
void mark_lambda_use_self();
|
||||||
bool class_exists(const StringName &p_class) const;
|
bool class_exists(const StringName &p_class) const;
|
||||||
Ref<GDScriptParserRef> get_parser_for(const String &p_path);
|
Ref<GDScriptParserRef> get_parser_for(const String &p_path);
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
|
|
|
@ -1211,8 +1211,8 @@ void GDScriptByteCodeGenerator::write_call_script_function(const Address &p_targ
|
||||||
append(p_function_name);
|
append(p_function_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GDScriptByteCodeGenerator::write_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures) {
|
void GDScriptByteCodeGenerator::write_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures, bool p_use_self) {
|
||||||
append(GDScriptFunction::OPCODE_CREATE_LAMBDA, 1 + p_captures.size());
|
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++) {
|
for (int i = 0; i < p_captures.size(); i++) {
|
||||||
append(p_captures[i]);
|
append(p_captures[i]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(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_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_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(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_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;
|
virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) override;
|
||||||
|
|
|
@ -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(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_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_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(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_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;
|
virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) = 0;
|
||||||
|
|
|
@ -1197,7 +1197,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
|
||||||
return GDScriptCodeGenerator::Address();
|
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++) {
|
for (int i = 0; i < captures.size(); i++) {
|
||||||
if (captures[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) {
|
if (captures[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) {
|
||||||
|
|
|
@ -792,6 +792,25 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
|
||||||
|
|
||||||
incr = 3 + captures_count;
|
incr = 3 + captures_count;
|
||||||
} break;
|
} 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: {
|
case OPCODE_JUMP: {
|
||||||
text += "jump ";
|
text += "jump ";
|
||||||
text += itos(_code_ptr[ip + 1]);
|
text += itos(_code_ptr[ip + 1]);
|
||||||
|
|
|
@ -299,6 +299,7 @@ public:
|
||||||
OPCODE_AWAIT,
|
OPCODE_AWAIT,
|
||||||
OPCODE_AWAIT_RESUME,
|
OPCODE_AWAIT_RESUME,
|
||||||
OPCODE_CREATE_LAMBDA,
|
OPCODE_CREATE_LAMBDA,
|
||||||
|
OPCODE_CREATE_SELF_LAMBDA,
|
||||||
OPCODE_JUMP,
|
OPCODE_JUMP,
|
||||||
OPCODE_JUMP_IF,
|
OPCODE_JUMP_IF,
|
||||||
OPCODE_JUMP_IF_NOT,
|
OPCODE_JUMP_IF_NOT,
|
||||||
|
|
|
@ -93,3 +93,81 @@ GDScriptLambdaCallable::GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptF
|
||||||
|
|
||||||
h = (uint32_t)hash_djb2_one_64((uint64_t)this);
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -62,4 +62,29 @@ public:
|
||||||
virtual ~GDScriptLambdaCallable() = default;
|
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
|
#endif // GDSCRIPT_LAMBDA_CALLABLE
|
||||||
|
|
|
@ -2200,9 +2200,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_self(ExpressionNode *p_pre
|
||||||
if (current_function && current_function->is_static) {
|
if (current_function && current_function->is_static) {
|
||||||
push_error(R"(Cannot use "self" inside a static function.)");
|
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>();
|
SelfNode *self = alloc_node<SelfNode>();
|
||||||
self->current_class = current_class;
|
self->current_class = current_class;
|
||||||
return self;
|
return self;
|
||||||
|
|
|
@ -767,6 +767,7 @@ public:
|
||||||
LOCAL_BIND, // Pattern bind.
|
LOCAL_BIND, // Pattern bind.
|
||||||
MEMBER_VARIABLE,
|
MEMBER_VARIABLE,
|
||||||
MEMBER_CONSTANT,
|
MEMBER_CONSTANT,
|
||||||
|
INHERITED_VARIABLE,
|
||||||
};
|
};
|
||||||
Source source = UNDEFINED_SOURCE;
|
Source source = UNDEFINED_SOURCE;
|
||||||
|
|
||||||
|
@ -800,6 +801,7 @@ public:
|
||||||
FunctionNode *parent_function = nullptr;
|
FunctionNode *parent_function = nullptr;
|
||||||
Vector<IdentifierNode *> captures;
|
Vector<IdentifierNode *> captures;
|
||||||
Map<StringName, int> captures_indices;
|
Map<StringName, int> captures_indices;
|
||||||
|
bool use_self = false;
|
||||||
|
|
||||||
bool has_name() const {
|
bool has_name() const {
|
||||||
return function && function->identifier;
|
return function && function->identifier;
|
||||||
|
|
|
@ -306,6 +306,7 @@ void (*type_init_function_table[])(Variant *) = {
|
||||||
&&OPCODE_AWAIT, \
|
&&OPCODE_AWAIT, \
|
||||||
&&OPCODE_AWAIT_RESUME, \
|
&&OPCODE_AWAIT_RESUME, \
|
||||||
&&OPCODE_CREATE_LAMBDA, \
|
&&OPCODE_CREATE_LAMBDA, \
|
||||||
|
&&OPCODE_CREATE_SELF_LAMBDA, \
|
||||||
&&OPCODE_JUMP, \
|
&&OPCODE_JUMP, \
|
||||||
&&OPCODE_JUMP_IF, \
|
&&OPCODE_JUMP_IF, \
|
||||||
&&OPCODE_JUMP_IF_NOT, \
|
&&OPCODE_JUMP_IF_NOT, \
|
||||||
|
@ -2277,6 +2278,41 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
|
||||||
}
|
}
|
||||||
DISPATCH_OPCODE;
|
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) {
|
OPCODE(OPCODE_JUMP) {
|
||||||
CHECK_SPACE(2);
|
CHECK_SPACE(2);
|
||||||
int to = _code_ptr[ip + 1];
|
int to = _code_ptr[ip + 1];
|
||||||
|
|
|
@ -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)
|
|
@ -0,0 +1,5 @@
|
||||||
|
GDTEST_OK
|
||||||
|
foo
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
true
|
Loading…
Add table
Reference in a new issue