GDScript: Add lambdas to the type analyzer

- Lambdas are always callables (no specific signature match).
- Captures from the current context are evaluated.
This commit is contained in:
George Marques 2021-03-26 09:03:16 -03:00
parent c6e66a43b0
commit 3155368093
No known key found for this signature in database
GPG key ID: 046BD46A3201E43D
5 changed files with 122 additions and 24 deletions

View file

@ -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<GDScriptParser::IdentifierNode *>(p_expression));
break;
case GDScriptParser::Node::LAMBDA:
reduce_lambda(static_cast<GDScriptParser::LambdaNode *>(p_expression));
break;
case GDScriptParser::Node::LITERAL:
reduce_literal(static_cast<GDScriptParser::LiteralNode *>(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<GDScriptParserRef> 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);
}