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);
}

View file

@ -42,6 +42,7 @@ class GDScriptAnalyzer {
HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
const GDScriptParser::EnumNode *current_enum = nullptr;
List<const GDScriptParser::LambdaNode *> lambda_stack;
Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true);
GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type);
@ -82,6 +83,7 @@ class GDScriptAnalyzer {
void reduce_get_node(GDScriptParser::GetNodeNode *p_get_node);
void reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin = false);
void reduce_identifier_from_base(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType *p_base = nullptr);
void reduce_lambda(GDScriptParser::LambdaNode *p_lambda);
void reduce_literal(GDScriptParser::LiteralNode *p_literal);
void reduce_preload(GDScriptParser::PreloadNode *p_preload);
void reduce_self(GDScriptParser::SelfNode *p_self);
@ -109,6 +111,7 @@ class GDScriptAnalyzer {
void mark_node_unsafe(const GDScriptParser::Node *p_node);
bool class_exists(const StringName &p_class) const;
Ref<GDScriptParserRef> get_parser_for(const String &p_path);
const GDScriptParser::LambdaNode *get_current_lambda() const;
#ifdef DEBUG_ENABLED
bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context);
#endif

View file

@ -1227,7 +1227,7 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod
} else {
p_function->parameters_indices[parameter->identifier->name] = p_function->parameters.size();
p_function->parameters.push_back(parameter);
p_body->add_local(parameter);
p_body->add_local(parameter, current_function);
}
} while (match(GDScriptTokenizer::Token::COMMA));
}
@ -1365,6 +1365,7 @@ bool GDScriptParser::register_annotation(const MethodInfo &p_info, uint32_t p_ta
GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, SuiteNode *p_suite, bool p_for_lambda) {
SuiteNode *suite = p_suite != nullptr ? p_suite : alloc_node<SuiteNode>();
suite->parent_block = current_suite;
suite->parent_function = current_function;
current_suite = suite;
bool multiline = false;
@ -1401,7 +1402,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context,
if (local.type != SuiteNode::Local::UNDEFINED) {
push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), variable->identifier->name));
}
current_suite->add_local(variable);
current_suite->add_local(variable, current_function);
break;
}
case Node::CONSTANT: {
@ -1416,7 +1417,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context,
}
push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, constant->identifier->name));
}
current_suite->add_local(constant);
current_suite->add_local(constant, current_function);
break;
}
default:
@ -1647,7 +1648,7 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
SuiteNode *suite = alloc_node<SuiteNode>();
if (n_for->variable) {
suite->add_local(SuiteNode::Local(n_for->variable));
suite->add_local(SuiteNode::Local(n_for->variable, current_function));
}
suite->parent_for = n_for;
@ -1802,7 +1803,7 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
branch->patterns[0]->binds.get_key_list(&binds);
for (List<StringName>::Element *E = binds.front(); E != nullptr; E = E->next()) {
SuiteNode::Local local(branch->patterns[0]->binds[E->get()]);
SuiteNode::Local local(branch->patterns[0]->binds[E->get()], current_function);
suite->add_local(local);
}
}
@ -2053,6 +2054,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode
if (current_suite != nullptr && current_suite->has_local(identifier->name)) {
const SuiteNode::Local &declaration = current_suite->get_local(identifier->name);
identifier->source_function = declaration.source_function;
switch (declaration.type) {
case SuiteNode::Local::CONSTANT:
identifier->source = IdentifierNode::LOCAL_CONSTANT;
@ -2731,7 +2734,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_
GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign) {
LambdaNode *lambda = alloc_node<LambdaNode>();
lambda->parent_function = current_function;
FunctionNode *function = alloc_node<FunctionNode>();
function->source_lambda = lambda;
function->is_static = current_function != nullptr ? current_function->is_static : false;
if (match(GDScriptTokenizer::Token::IDENTIFIER)) {
function->identifier = parse_identifier();
@ -4024,6 +4031,14 @@ void GDScriptParser::TreePrinter::print_if(IfNode *p_if, bool p_is_elif) {
void GDScriptParser::TreePrinter::print_lambda(LambdaNode *p_lambda) {
print_function(p_lambda->function, "Lambda");
push_text("| captures [ ");
for (const Map<StringName, IdentifierNode *>::Element *E = p_lambda->captures.front(); E; E = E->next()) {
push_text(E->key().operator String());
if (E->next()) {
push_text(" , ");
}
}
push_line(" ]");
}
void GDScriptParser::TreePrinter::print_literal(LiteralNode *p_literal) {

View file

@ -76,6 +76,7 @@ public:
struct GetNodeNode;
struct IdentifierNode;
struct IfNode;
struct LambdaNode;
struct LiteralNode;
struct MatchNode;
struct MatchBranchNode;
@ -729,6 +730,7 @@ public:
bool is_coroutine = false;
MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
MethodInfo info;
LambdaNode *source_lambda = nullptr;
#ifdef TOOLS_ENABLED
Vector<Variant> default_arg_values;
String doc_description;
@ -772,6 +774,7 @@ public:
VariableNode *variable_source;
IdentifierNode *bind_source;
};
FunctionNode *source_function = nullptr;
int usages = 0; // Useful for binds/iterator variable.
@ -792,6 +795,8 @@ public:
struct LambdaNode : public ExpressionNode {
FunctionNode *function = nullptr;
FunctionNode *parent_function = nullptr;
Map<StringName, IdentifierNode *> captures;
bool has_name() const {
return function && function->identifier;
@ -955,6 +960,7 @@ public:
IdentifierNode *bind;
};
StringName name;
FunctionNode *source_function = nullptr;
int start_line = 0, end_line = 0;
int start_column = 0, end_column = 0;
@ -964,10 +970,11 @@ public:
String get_name() const;
Local() {}
Local(ConstantNode *p_constant) {
Local(ConstantNode *p_constant, FunctionNode *p_source_function) {
type = CONSTANT;
constant = p_constant;
name = p_constant->identifier->name;
source_function = p_source_function;
start_line = p_constant->start_line;
end_line = p_constant->end_line;
@ -976,10 +983,11 @@ public:
leftmost_column = p_constant->leftmost_column;
rightmost_column = p_constant->rightmost_column;
}
Local(VariableNode *p_variable) {
Local(VariableNode *p_variable, FunctionNode *p_source_function) {
type = VARIABLE;
variable = p_variable;
name = p_variable->identifier->name;
source_function = p_source_function;
start_line = p_variable->start_line;
end_line = p_variable->end_line;
@ -988,10 +996,11 @@ public:
leftmost_column = p_variable->leftmost_column;
rightmost_column = p_variable->rightmost_column;
}
Local(ParameterNode *p_parameter) {
Local(ParameterNode *p_parameter, FunctionNode *p_source_function) {
type = PARAMETER;
parameter = p_parameter;
name = p_parameter->identifier->name;
source_function = p_source_function;
start_line = p_parameter->start_line;
end_line = p_parameter->end_line;
@ -1000,10 +1009,11 @@ public:
leftmost_column = p_parameter->leftmost_column;
rightmost_column = p_parameter->rightmost_column;
}
Local(IdentifierNode *p_identifier) {
Local(IdentifierNode *p_identifier, FunctionNode *p_source_function) {
type = FOR_VARIABLE;
bind = p_identifier;
name = p_identifier->name;
source_function = p_source_function;
start_line = p_identifier->start_line;
end_line = p_identifier->end_line;
@ -1028,9 +1038,9 @@ public:
bool has_local(const StringName &p_name) const;
const Local &get_local(const StringName &p_name) const;
template <class T>
void add_local(T *p_local) {
void add_local(T *p_local, FunctionNode *p_source_function) {
locals_indices[p_local->identifier->name] = locals.size();
locals.push_back(Local(p_local));
locals.push_back(Local(p_local, p_source_function));
}
void add_local(const Local &p_local) {
locals_indices[p_local.name] = locals.size();

View file

@ -118,6 +118,18 @@ static void test_parser(const String &p_code, const String &p_script_path, const
print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message));
}
}
GDScriptAnalyzer analyzer(&parser);
analyzer.analyze();
if (err != OK) {
const List<GDScriptParser::ParserError> &errors = parser.get_errors();
for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) {
const GDScriptParser::ParserError &error = E->get();
print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message));
}
}
#ifdef TOOLS_ENABLED
GDScriptParser::TreePrinter printer;
printer.print_tree(parser);