From c6e66a43b0eae569b60c85d3f26009ed52f97861 Mon Sep 17 00:00:00 2001 From: George Marques Date: Thu, 25 Mar 2021 10:36:29 -0300 Subject: GDScript: Add lambda syntax parsing Lambda syntax is the same as a the function syntax (using the same `func` keyword) except that the name is optional and it can be embedded anywhere an expression is expected. E.g.: func _ready(): var my_lambda = func(x): print(x) my_lambda.call("hello") --- modules/gdscript/gdscript_analyzer.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'modules/gdscript/gdscript_analyzer.cpp') diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 5da2bb5cc1..e754ba70ec 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -872,6 +872,9 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) { case GDScriptParser::Node::SIGNAL: // Nothing to do. break; + case GDScriptParser::Node::LAMBDA: + // FIXME: Recurse into lambda. + break; } } @@ -1489,6 +1492,7 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre case GDScriptParser::Node::FOR: case GDScriptParser::Node::FUNCTION: case GDScriptParser::Node::IF: + case GDScriptParser::Node::LAMBDA: case GDScriptParser::Node::MATCH: case GDScriptParser::Node::MATCH_BRANCH: case GDScriptParser::Node::PARAMETER: -- cgit v1.2.3 From 3155368093875e644b1adbfa29bb584134c52a89 Mon Sep 17 00:00:00 2001 From: George Marques Date: Fri, 26 Mar 2021 09:03:16 -0300 Subject: GDScript: Add lambdas to the type analyzer - Lambdas are always callables (no specific signature match). - Captures from the current context are evaluated. --- modules/gdscript/gdscript_analyzer.cpp | 84 ++++++++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 13 deletions(-) (limited to 'modules/gdscript/gdscript_analyzer.cpp') diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index e754ba70ec..3780f6e23c 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -856,6 +856,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) { case GDScriptParser::Node::DICTIONARY: case GDScriptParser::Node::GET_NODE: case GDScriptParser::Node::IDENTIFIER: + case GDScriptParser::Node::LAMBDA: case GDScriptParser::Node::LITERAL: case GDScriptParser::Node::PRELOAD: case GDScriptParser::Node::SELF: @@ -872,9 +873,6 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) { case GDScriptParser::Node::SIGNAL: // Nothing to do. break; - case GDScriptParser::Node::LAMBDA: - // FIXME: Recurse into lambda. - break; } } @@ -1461,6 +1459,9 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre case GDScriptParser::Node::IDENTIFIER: reduce_identifier(static_cast(p_expression)); break; + case GDScriptParser::Node::LAMBDA: + reduce_lambda(static_cast(p_expression)); + break; case GDScriptParser::Node::LITERAL: reduce_literal(static_cast(p_expression)); break; @@ -1492,7 +1493,6 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre case GDScriptParser::Node::FOR: case GDScriptParser::Node::FUNCTION: case GDScriptParser::Node::IF: - case GDScriptParser::Node::LAMBDA: case GDScriptParser::Node::MATCH: case GDScriptParser::Node::MATCH_BRANCH: case GDScriptParser::Node::PARAMETER: @@ -2350,6 +2350,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod case GDScriptParser::ClassNode::Member::ENUM_VALUE: p_identifier->is_constant = true; p_identifier->reduced_value = member.enum_value.value; + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; break; case GDScriptParser::ClassNode::Member::VARIABLE: p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE; @@ -2450,42 +2451,65 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident } } + bool found_source = false; // Check if identifier is local. // If that's the case, the declaration already was solved before. switch (p_identifier->source) { case GDScriptParser::IdentifierNode::FUNCTION_PARAMETER: p_identifier->set_datatype(p_identifier->parameter_source->get_datatype()); - return; + found_source = true; + break; case GDScriptParser::IdentifierNode::LOCAL_CONSTANT: case GDScriptParser::IdentifierNode::MEMBER_CONSTANT: p_identifier->set_datatype(p_identifier->constant_source->get_datatype()); p_identifier->is_constant = true; // TODO: Constant should have a value on the node itself. p_identifier->reduced_value = p_identifier->constant_source->initializer->reduced_value; - return; + found_source = true; + break; case GDScriptParser::IdentifierNode::MEMBER_VARIABLE: p_identifier->variable_source->usages++; [[fallthrough]]; case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: p_identifier->set_datatype(p_identifier->variable_source->get_datatype()); - return; + found_source = true; + break; case GDScriptParser::IdentifierNode::LOCAL_ITERATOR: p_identifier->set_datatype(p_identifier->bind_source->get_datatype()); - return; + found_source = true; + break; case GDScriptParser::IdentifierNode::LOCAL_BIND: { GDScriptParser::DataType result = p_identifier->bind_source->get_datatype(); result.is_constant = true; p_identifier->set_datatype(result); - return; - } + found_source = true; + } break; case GDScriptParser::IdentifierNode::UNDEFINED_SOURCE: break; } // Not a local, so check members. - reduce_identifier_from_base(p_identifier); - if (p_identifier->get_datatype().is_set()) { - // Found. + if (!found_source) { + reduce_identifier_from_base(p_identifier); + if (p_identifier->source != GDScriptParser::IdentifierNode::UNDEFINED_SOURCE || p_identifier->get_datatype().is_set()) { + // Found. + found_source = true; + } + } + + 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; + } + + 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; + } return; } @@ -2567,6 +2591,33 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident p_identifier->set_datatype(dummy); // Just so type is set to something. } +void GDScriptAnalyzer::reduce_lambda(GDScriptParser::LambdaNode *p_lambda) { + // Lambda is always a Callable. + GDScriptParser::DataType lambda_type; + lambda_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + lambda_type.kind = GDScriptParser::DataType::BUILTIN; + lambda_type.builtin_type = Variant::CALLABLE; + p_lambda->set_datatype(lambda_type); + + if (p_lambda->function == nullptr) { + return; + } + + GDScriptParser::FunctionNode *previous_function = parser->current_function; + parser->current_function = p_lambda->function; + + lambda_stack.push_back(p_lambda); + + for (int i = 0; i < p_lambda->function->parameters.size(); i++) { + resolve_parameter(p_lambda->function->parameters[i]); + } + + resolve_suite(p_lambda->function->body); + + lambda_stack.pop_back(); + parser->current_function = previous_function; +} + void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) { p_literal->reduced_value = p_literal->value; p_literal->is_constant = true; @@ -3526,6 +3577,13 @@ Ref GDScriptAnalyzer::get_parser_for(const String &p_path) { return ref; } +const GDScriptParser::LambdaNode *GDScriptAnalyzer::get_current_lambda() const { + if (lambda_stack.size()) { + return lambda_stack.back()->get(); + } + return nullptr; +} + Error GDScriptAnalyzer::resolve_inheritance() { return resolve_inheritance(parser->head); } -- cgit v1.2.3 From c201b212c7d188c15de78c4d4150e9d7337b6983 Mon Sep 17 00:00:00 2001 From: George Marques Date: Sun, 28 Mar 2021 11:03:13 -0300 Subject: GDScript: Implement lambdas compilation and runtime --- modules/gdscript/gdscript_analyzer.cpp | 35 +++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) (limited to 'modules/gdscript/gdscript_analyzer.cpp') diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 3780f6e23c..db554c1ab0 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -2101,6 +2101,8 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa 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->callee); + } 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->callee); } call_type = return_type; @@ -2223,6 +2225,8 @@ void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) if (!ClassDB::is_parent_class(GDScriptParser::get_real_class_name(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); } p_get_node->set_datatype(result); @@ -2614,6 +2618,30 @@ void GDScriptAnalyzer::reduce_lambda(GDScriptParser::LambdaNode *p_lambda) { resolve_suite(p_lambda->function->body); + int captures_amount = p_lambda->captures.size(); + if (captures_amount > 0) { + // Create space for lambda parameters. + // At the beginning to not mess with optional parameters. + int param_count = p_lambda->function->parameters.size(); + p_lambda->function->parameters.resize(param_count + captures_amount); + for (int i = param_count - 1; i >= 0; i--) { + p_lambda->function->parameters.write[i + captures_amount] = p_lambda->function->parameters[i]; + p_lambda->function->parameters_indices[p_lambda->function->parameters[i]->identifier->name] = i + captures_amount; + } + + // Add captures as extra parameters at the beginning. + for (int i = 0; i < p_lambda->captures.size(); i++) { + GDScriptParser::IdentifierNode *capture = p_lambda->captures[i]; + GDScriptParser::ParameterNode *capture_param = parser->alloc_node(); + capture_param->identifier = capture; + capture_param->usages = capture->usages; + capture_param->set_datatype(capture->get_datatype()); + + p_lambda->function->parameters.write[i] = capture_param; + p_lambda->function->parameters_indices[capture->name] = i; + } + } + lambda_stack.pop_back(); parser->current_function = previous_function; } @@ -3577,13 +3605,6 @@ Ref GDScriptAnalyzer::get_parser_for(const String &p_path) { return ref; } -const GDScriptParser::LambdaNode *GDScriptAnalyzer::get_current_lambda() const { - if (lambda_stack.size()) { - return lambda_stack.back()->get(); - } - return nullptr; -} - Error GDScriptAnalyzer::resolve_inheritance() { return resolve_inheritance(parser->head); } -- cgit v1.2.3 From 9ed0f0384cd73921f2c85d1e030fbb596b0954ea Mon Sep 17 00:00:00 2001 From: George Marques Date: Mon, 5 Apr 2021 11:17:59 -0300 Subject: GDScript: Fix crash when base of an attribute is invalid In attribute expressions (`a.b`) it's possible that the base has an incorrect syntax and thus become a nullptr expression in the tree. This commit add the check for this case to fail gracefully instead of crashing. --- modules/gdscript/gdscript_analyzer.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'modules/gdscript/gdscript_analyzer.cpp') diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index db554c1ab0..17ae52f3ab 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -2065,6 +2065,12 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa is_self = true; } else if (callee_type == GDScriptParser::Node::SUBSCRIPT) { GDScriptParser::SubscriptNode *subscript = static_cast(p_call->callee); + if (subscript->base == nullptr) { + // Invalid syntax, error already set on parser. + p_call->set_datatype(call_type); + mark_node_unsafe(p_call); + return; + } if (!subscript->is_attribute) { // Invalid call. Error already sent in parser. // TODO: Could check if Callable here. -- cgit v1.2.3