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:
parent
c6e66a43b0
commit
3155368093
|
@ -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.
|
||||
if (!found_source) {
|
||||
reduce_identifier_from_base(p_identifier);
|
||||
if (p_identifier->get_datatype().is_set()) {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue