summaryrefslogtreecommitdiff
path: root/modules/gdscript
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript')
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp11
-rw-r--r--modules/gdscript/gdscript.cpp27
-rw-r--r--modules/gdscript/gdscript.h2
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp160
-rw-r--r--modules/gdscript/gdscript_analyzer.h2
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp393
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h66
-rw-r--r--modules/gdscript/gdscript_cache.cpp4
-rw-r--r--modules/gdscript/gdscript_codegen.h13
-rw-r--r--modules/gdscript/gdscript_compiler.cpp183
-rw-r--r--modules/gdscript/gdscript_compiler.h4
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp92
-rw-r--r--modules/gdscript/gdscript_editor.cpp15
-rw-r--r--modules/gdscript/gdscript_function.cpp4
-rw-r--r--modules/gdscript/gdscript_function.h41
-rw-r--r--modules/gdscript/gdscript_lambda_callable.cpp95
-rw-r--r--modules/gdscript/gdscript_lambda_callable.h65
-rw-r--r--modules/gdscript/gdscript_parser.cpp268
-rw-r--r--modules/gdscript/gdscript_parser.h54
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp14
-rw-r--r--modules/gdscript/gdscript_tokenizer.h3
-rw-r--r--modules/gdscript/gdscript_vm.cpp206
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.cpp88
-rw-r--r--modules/gdscript/language_server/gdscript_language_protocol.cpp3
-rw-r--r--modules/gdscript/language_server/gdscript_language_protocol.h4
-rw-r--r--modules/gdscript/language_server/gdscript_language_server.cpp2
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.cpp1
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.cpp11
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.h1
-rw-r--r--modules/gdscript/language_server/lsp.hpp114
-rw-r--r--modules/gdscript/register_types.cpp8
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp583
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.h126
-rw-r--r--modules/gdscript/tests/gdscript_test_runner_suite.h74
-rw-r--r--modules/gdscript/tests/scripts/.gitignore2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/missing_argument.gd6
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/missing_argument.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/missing_colon.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/missing_colon.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd6
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out3
-rw-r--r--modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.gd7
-rw-r--r--modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/variable_declaration.gd12
-rw-r--r--modules/gdscript/tests/scripts/parser/features/variable_declaration.out7
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/unused_variable.out5
-rw-r--r--modules/gdscript/tests/scripts/project.godot10
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd5
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out6
-rw-r--r--modules/gdscript/tests/test_gdscript.cpp94
-rw-r--r--modules/gdscript/tests/test_gdscript.h8
66 files changed, 2571 insertions, 388 deletions
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index ccc942d86b..d34cccb3af 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -485,10 +485,15 @@ void GDScriptSyntaxHighlighter::_update_cache() {
/* Reserved words. */
const Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color");
+ const Color control_flow_keyword_color = EDITOR_GET("text_editor/highlighting/control_flow_keyword_color");
List<String> keyword_list;
gdscript->get_reserved_words(&keyword_list);
for (List<String>::Element *E = keyword_list.front(); E; E = E->next()) {
- keywords[E->get()] = keyword_color;
+ if (gdscript->is_control_flow_keyword(E->get())) {
+ keywords[E->get()] = control_flow_keyword_color;
+ } else {
+ keywords[E->get()] = keyword_color;
+ }
}
/* Comments */
@@ -541,7 +546,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
}
const String text_edit_color_theme = EditorSettings::get_singleton()->get("text_editor/theme/color_theme");
- const bool default_theme = text_edit_color_theme == "Default";
+ const bool default_theme = text_edit_color_theme == "Godot 2";
if (default_theme || EditorSettings::get_singleton()->is_dark_theme()) {
function_definition_color = Color(0.4, 0.9, 1.0);
@@ -553,7 +558,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
EDITOR_DEF("text_editor/highlighting/gdscript/function_definition_color", function_definition_color);
EDITOR_DEF("text_editor/highlighting/gdscript/node_path_color", node_path_color);
- if (text_edit_color_theme == "Adaptive" || default_theme) {
+ if (text_edit_color_theme == "Default" || default_theme) {
EditorSettings::get_singleton()->set_initial_value(
"text_editor/highlighting/gdscript/function_definition_color",
function_definition_color,
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index c9c5d00aa5..d7814e85b0 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -45,6 +45,10 @@
#include "gdscript_parser.h"
#include "gdscript_warning.h"
+#ifdef TESTS_ENABLED
+#include "tests/gdscript_test_runner.h"
+#endif
+
///////////////////////////
GDScriptNativeClass::GDScriptNativeClass(const StringName &p_name) {
@@ -477,7 +481,7 @@ void GDScript::_update_doc() {
methods[i].return_val.class_name = _get_gdscript_reference_class_name(Object::cast_to<GDScript>(return_type.script_type));
}
- // Change class name if argumetn is script reference.
+ // Change class name if argument is script reference.
for (int j = 0; j < fn->get_argument_count(); j++) {
GDScriptDataType arg_type = fn->get_argument_type(j);
if (arg_type.kind == GDScriptDataType::GDSCRIPT) {
@@ -1041,10 +1045,10 @@ Error GDScript::load_source_code(const String &p_path) {
ERR_FAIL_COND_V(err, err);
}
- int len = f->get_len();
+ uint64_t len = f->get_length();
sourcef.resize(len + 1);
uint8_t *w = sourcef.ptrw();
- int r = f->get_buffer(w, len);
+ uint64_t r = f->get_buffer(w, len);
f->close();
memdelete(f);
ERR_FAIL_COND_V(r != len, ERR_CANT_OPEN);
@@ -1766,6 +1770,10 @@ void GDScriptLanguage::init() {
for (List<Engine::Singleton>::Element *E = singletons.front(); E; E = E->next()) {
_add_global(E->get().name, E->get().ptr);
}
+
+#ifdef TESTS_ENABLED
+ GDScriptTests::GDScriptTestRunner::handle_cmdline();
+#endif
}
String GDScriptLanguage::get_type() const {
@@ -2127,6 +2135,19 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
}
}
+bool GDScriptLanguage::is_control_flow_keyword(String p_keyword) const {
+ return p_keyword == "break" ||
+ p_keyword == "continue" ||
+ p_keyword == "elif" ||
+ p_keyword == "else" ||
+ p_keyword == "if" ||
+ p_keyword == "for" ||
+ p_keyword == "match" ||
+ p_keyword == "pass" ||
+ p_keyword == "return" ||
+ p_keyword == "while";
+}
+
bool GDScriptLanguage::handles_global_class_type(const String &p_type) const {
return p_type == "GDScript";
}
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 12c909fd4f..6df66e876d 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -270,6 +270,7 @@ public:
class GDScriptInstance : public ScriptInstance {
friend class GDScript;
friend class GDScriptFunction;
+ friend class GDScriptLambdaCallable;
friend class GDScriptCompiler;
friend struct GDScriptUtilityFunctionsDefinitions;
@@ -460,6 +461,7 @@ public:
/* EDITOR FUNCTIONS */
virtual void get_reserved_words(List<String> *p_words) const;
+ virtual bool is_control_flow_keyword(String p_keywords) const;
virtual void get_comment_delimiters(List<String> *p_delimiters) const;
virtual void get_string_delimiters(List<String> *p_delimiters) const;
virtual String _get_processed_template(const String &p_template, const String &p_base_class_name) const;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index bdca64c146..d8d60b35c6 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -543,6 +543,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
} else {
// TODO: Add warning.
mark_node_unsafe(member.variable->initializer);
+ member.variable->use_conversion_assign = true;
}
} else if (datatype.builtin_type == Variant::INT && member.variable->initializer->get_datatype().builtin_type == Variant::FLOAT) {
#ifdef DEBUG_ENABLED
@@ -552,6 +553,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
if (member.variable->initializer->get_datatype().is_variant()) {
// TODO: Warn unsafe assign.
mark_node_unsafe(member.variable->initializer);
+ member.variable->use_conversion_assign = true;
}
}
} else if (member.variable->infer_datatype) {
@@ -856,6 +858,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:
@@ -1144,6 +1147,7 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable
} else {
// TODO: Add warning.
mark_node_unsafe(p_variable->initializer);
+ p_variable->use_conversion_assign = true;
}
#ifdef DEBUG_ENABLED
} else if (type.builtin_type == Variant::INT && p_variable->initializer->get_datatype().builtin_type == Variant::FLOAT) {
@@ -1153,6 +1157,7 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable
if (p_variable->initializer->get_datatype().is_variant()) {
// TODO: Warn unsafe assign.
mark_node_unsafe(p_variable->initializer);
+ p_variable->use_conversion_assign = true;
}
}
} else if (p_variable->infer_datatype) {
@@ -1458,6 +1463,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;
@@ -1604,10 +1612,12 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
} else {
// TODO: Add warning.
mark_node_unsafe(p_assignment);
+ p_assignment->use_conversion_assign = true;
}
} else {
// TODO: Warning in this case.
mark_node_unsafe(p_assignment);
+ p_assignment->use_conversion_assign = true;
}
}
} else {
@@ -1617,6 +1627,9 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
if (assignee_type.has_no_type() || assigned_value_type.is_variant()) {
mark_node_unsafe(p_assignment);
+ if (assignee_type.is_hard_type()) {
+ p_assignment->use_conversion_assign = true;
+ }
}
if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) {
@@ -2055,12 +2068,20 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
if (p_call->is_super) {
base_type = parser->current_class->base_type;
+ base_type.is_meta_type = false;
is_self = true;
} else if (callee_type == GDScriptParser::Node::IDENTIFIER) {
base_type = parser->current_class->get_datatype();
+ base_type.is_meta_type = false;
is_self = true;
} else if (callee_type == GDScriptParser::Node::SUBSCRIPT) {
GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(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.
@@ -2068,9 +2089,23 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
mark_node_unsafe(p_call);
return;
}
- reduce_expression(subscript->base);
+ if (subscript->attribute == nullptr) {
+ // Invalid call. Error already sent in parser.
+ p_call->set_datatype(call_type);
+ mark_node_unsafe(p_call);
+ return;
+ }
- base_type = subscript->base->get_datatype();
+ GDScriptParser::IdentifierNode *base_id = nullptr;
+ if (subscript->base->type == GDScriptParser::Node::IDENTIFIER) {
+ base_id = static_cast<GDScriptParser::IdentifierNode *>(subscript->base);
+ }
+ if (base_id && GDScriptParser::get_builtin_type(base_id->name) < Variant::VARIANT_MAX) {
+ base_type = make_builtin_meta_type(GDScriptParser::get_builtin_type(base_id->name));
+ } else {
+ reduce_expression(subscript->base);
+ base_type = subscript->base->get_datatype();
+ }
} else {
// Invalid call. Error already sent in parser.
// TODO: Could check if Callable here too.
@@ -2097,6 +2132,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;
@@ -2219,6 +2256,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);
@@ -2346,6 +2385,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;
@@ -2446,42 +2486,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;
}
@@ -2563,6 +2626,57 @@ 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);
+
+ 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<GDScriptParser::ParameterNode>();
+ 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;
+}
+
void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) {
p_literal->reduced_value = p_literal->value;
p_literal->is_constant = true;
@@ -2621,25 +2735,6 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
GDScriptParser::DataType result_type;
- // Reduce index first. If it's a constant StringName, use attribute instead.
- if (!p_subscript->is_attribute) {
- if (p_subscript->index == nullptr) {
- return;
- }
- reduce_expression(p_subscript->index);
-
- if (p_subscript->index->is_constant && p_subscript->index->reduced_value.get_type() == Variant::STRING_NAME) {
- GDScriptParser::IdentifierNode *attribute = parser->alloc_node<GDScriptParser::IdentifierNode>();
- // Copy location for better error message.
- attribute->start_line = p_subscript->index->start_line;
- attribute->end_line = p_subscript->index->end_line;
- attribute->leftmost_column = p_subscript->index->leftmost_column;
- attribute->rightmost_column = p_subscript->index->rightmost_column;
- p_subscript->is_attribute = true;
- p_subscript->attribute = attribute;
- }
- }
-
if (p_subscript->is_attribute) {
if (p_subscript->attribute == nullptr) {
return;
@@ -2682,7 +2777,10 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
}
}
} else {
- // Index was already reduced before.
+ if (p_subscript->index == nullptr) {
+ return;
+ }
+ reduce_expression(p_subscript->index);
if (p_subscript->base->is_constant && p_subscript->index->is_constant) {
// Just try to get it.
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 8430d3f4a5..aabf407c76 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -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);
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index af2bfc33a7..77a972ef12 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -47,7 +47,8 @@ uint32_t GDScriptByteCodeGenerator::add_parameter(const StringName &p_name, bool
}
uint32_t GDScriptByteCodeGenerator::add_local(const StringName &p_name, const GDScriptDataType &p_type) {
- int stack_pos = increase_stack();
+ int stack_pos = locals.size() + RESERVED_STACK;
+ locals.push_back(StackSlot(p_type.builtin_type));
add_stack_identifier(p_name, stack_pos);
return stack_pos;
}
@@ -66,25 +67,81 @@ uint32_t GDScriptByteCodeGenerator::add_or_get_name(const StringName &p_name) {
return get_name_map_pos(p_name);
}
-uint32_t GDScriptByteCodeGenerator::add_temporary() {
- current_temporaries++;
- int idx = increase_stack();
-#ifdef DEBUG_ENABLED
- temp_stack.push_back(idx);
-#endif
- return idx;
+uint32_t GDScriptByteCodeGenerator::add_temporary(const GDScriptDataType &p_type) {
+ Variant::Type temp_type = Variant::NIL;
+ if (p_type.has_type) {
+ if (p_type.kind == GDScriptDataType::BUILTIN) {
+ switch (p_type.builtin_type) {
+ case Variant::NIL:
+ case Variant::BOOL:
+ case Variant::INT:
+ case Variant::FLOAT:
+ case Variant::STRING:
+ case Variant::VECTOR2:
+ case Variant::VECTOR2I:
+ case Variant::RECT2:
+ case Variant::RECT2I:
+ case Variant::VECTOR3:
+ case Variant::VECTOR3I:
+ case Variant::TRANSFORM2D:
+ case Variant::PLANE:
+ case Variant::QUAT:
+ case Variant::AABB:
+ case Variant::BASIS:
+ case Variant::TRANSFORM:
+ case Variant::COLOR:
+ case Variant::STRING_NAME:
+ case Variant::NODE_PATH:
+ case Variant::RID:
+ case Variant::OBJECT:
+ case Variant::CALLABLE:
+ case Variant::SIGNAL:
+ case Variant::DICTIONARY:
+ case Variant::ARRAY:
+ temp_type = p_type.builtin_type;
+ break;
+ case Variant::PACKED_BYTE_ARRAY:
+ case Variant::PACKED_INT32_ARRAY:
+ case Variant::PACKED_INT64_ARRAY:
+ case Variant::PACKED_FLOAT32_ARRAY:
+ case Variant::PACKED_FLOAT64_ARRAY:
+ case Variant::PACKED_STRING_ARRAY:
+ case Variant::PACKED_VECTOR2_ARRAY:
+ case Variant::PACKED_VECTOR3_ARRAY:
+ case Variant::PACKED_COLOR_ARRAY:
+ case Variant::VARIANT_MAX:
+ // Packed arrays are reference counted, so we don't use the pool for them.
+ temp_type = Variant::NIL;
+ break;
+ }
+ } else {
+ temp_type = Variant::OBJECT;
+ }
+ }
+
+ if (!temporaries_pool.has(temp_type)) {
+ temporaries_pool[temp_type] = List<int>();
+ }
+
+ List<int> &pool = temporaries_pool[temp_type];
+ if (pool.is_empty()) {
+ StackSlot new_temp(temp_type);
+ int idx = temporaries.size();
+ pool.push_back(idx);
+ temporaries.push_back(new_temp);
+ }
+ int slot = pool.front()->get();
+ pool.pop_front();
+ used_temporaries.push_back(slot);
+ return slot;
}
void GDScriptByteCodeGenerator::pop_temporary() {
- ERR_FAIL_COND(current_temporaries == 0);
- current_stack_size--;
-#ifdef DEBUG_ENABLED
- if (temp_stack.back()->get() != current_stack_size) {
- ERR_PRINT("Mismatched popping of temporary value");
- }
- temp_stack.pop_back();
-#endif
- current_temporaries--;
+ ERR_FAIL_COND(used_temporaries.is_empty());
+ int slot_idx = used_temporaries.back()->get();
+ const StackSlot &slot = temporaries[slot_idx];
+ temporaries_pool[slot.type].push_back(slot_idx);
+ used_temporaries.pop_back();
}
void GDScriptByteCodeGenerator::start_parameters() {
@@ -119,12 +176,22 @@ void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName
GDScriptFunction *GDScriptByteCodeGenerator::write_end() {
#ifdef DEBUG_ENABLED
- if (current_temporaries != 0) {
- ERR_PRINT("Non-zero temporary variables at end of function: " + itos(current_temporaries));
+ if (!used_temporaries.is_empty()) {
+ ERR_PRINT("Non-zero temporary variables at end of function: " + itos(used_temporaries.size()));
}
#endif
append(GDScriptFunction::OPCODE_END, 0);
+ for (int i = 0; i < temporaries.size(); i++) {
+ int stack_index = i + max_locals + RESERVED_STACK;
+ for (int j = 0; j < temporaries[i].bytecode_indices.size(); j++) {
+ opcodes.write[temporaries[i].bytecode_indices[j]] = stack_index | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
+ }
+ if (temporaries[i].type != Variant::NIL) {
+ function->temporary_slots[stack_index] = temporaries[i].type;
+ }
+ }
+
if (constant_map.size()) {
function->_constant_count = constant_map.size();
function->constants.resize(constant_map.size());
@@ -314,10 +381,22 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() {
function->_methods_count = 0;
}
+ if (lambdas_map.size()) {
+ function->lambdas.resize(lambdas_map.size());
+ function->_lambdas_ptr = function->lambdas.ptrw();
+ function->_lambdas_count = lambdas_map.size();
+ for (const Map<GDScriptFunction *, int>::Element *E = lambdas_map.front(); E; E = E->next()) {
+ function->lambdas.write[E->get()] = E->key();
+ }
+ } else {
+ function->_lambdas_ptr = nullptr;
+ function->_lambdas_count = 0;
+ }
+
if (debug_stack) {
function->stack_debug = stack_debug;
}
- function->_stack_size = stack_max;
+ function->_stack_size = RESERVED_STACK + max_locals + temporaries.size();
function->_instruction_args_size = instr_args_max;
function->_ptrcall_args_size = ptrcall_max;
@@ -341,6 +420,117 @@ void GDScriptByteCodeGenerator::set_initial_line(int p_line) {
#define IS_BUILTIN_TYPE(m_var, m_type) \
(m_var.type.has_type && m_var.type.kind == GDScriptDataType::BUILTIN && m_var.type.builtin_type == m_type)
+void GDScriptByteCodeGenerator::write_type_adjust(const Address &p_target, Variant::Type p_new_type) {
+ switch (p_new_type) {
+ case Variant::BOOL:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_BOOL, 1);
+ break;
+ case Variant::INT:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_INT, 1);
+ break;
+ case Variant::FLOAT:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_FLOAT, 1);
+ break;
+ case Variant::STRING:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_STRING, 1);
+ break;
+ case Variant::VECTOR2:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR2, 1);
+ break;
+ case Variant::VECTOR2I:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR2I, 1);
+ break;
+ case Variant::RECT2:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_RECT2, 1);
+ break;
+ case Variant::RECT2I:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_RECT2I, 1);
+ break;
+ case Variant::VECTOR3:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR3, 1);
+ break;
+ case Variant::VECTOR3I:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR3I, 1);
+ break;
+ case Variant::TRANSFORM2D:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_TRANSFORM2D, 1);
+ break;
+ case Variant::PLANE:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_PLANE, 1);
+ break;
+ case Variant::QUAT:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_QUAT, 1);
+ break;
+ case Variant::AABB:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_AABB, 1);
+ break;
+ case Variant::BASIS:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_BASIS, 1);
+ break;
+ case Variant::TRANSFORM:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_TRANSFORM, 1);
+ break;
+ case Variant::COLOR:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_COLOR, 1);
+ break;
+ case Variant::STRING_NAME:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_STRING_NAME, 1);
+ break;
+ case Variant::NODE_PATH:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_NODE_PATH, 1);
+ break;
+ case Variant::RID:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_RID, 1);
+ break;
+ case Variant::OBJECT:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_OBJECT, 1);
+ break;
+ case Variant::CALLABLE:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_CALLABLE, 1);
+ break;
+ case Variant::SIGNAL:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_SIGNAL, 1);
+ break;
+ case Variant::DICTIONARY:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_DICTIONARY, 1);
+ break;
+ case Variant::ARRAY:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_ARRAY, 1);
+ break;
+ case Variant::PACKED_BYTE_ARRAY:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_BYTE_ARRAY, 1);
+ break;
+ case Variant::PACKED_INT32_ARRAY:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_INT32_ARRAY, 1);
+ break;
+ case Variant::PACKED_INT64_ARRAY:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_INT64_ARRAY, 1);
+ break;
+ case Variant::PACKED_FLOAT32_ARRAY:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_FLOAT32_ARRAY, 1);
+ break;
+ case Variant::PACKED_FLOAT64_ARRAY:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_FLOAT64_ARRAY, 1);
+ break;
+ case Variant::PACKED_STRING_ARRAY:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_STRING_ARRAY, 1);
+ break;
+ case Variant::PACKED_VECTOR2_ARRAY:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_VECTOR2_ARRAY, 1);
+ break;
+ case Variant::PACKED_VECTOR3_ARRAY:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_VECTOR3_ARRAY, 1);
+ break;
+ case Variant::PACKED_COLOR_ARRAY:
+ append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_COLOR_ARRAY, 1);
+ break;
+ case Variant::NIL:
+ case Variant::VARIANT_MAX:
+ return;
+ }
+ append(p_target);
+}
+
void GDScriptByteCodeGenerator::write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) {
if (HAS_BUILTIN_TYPE(p_left_operand)) {
// Gather specific operator.
@@ -589,63 +779,43 @@ void GDScriptByteCodeGenerator::write_get_member(const Address &p_target, const
append(p_name);
}
-void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Address &p_source) {
- if (p_target.type.has_type && !p_source.type.has_type) {
- // Typed assignment.
- switch (p_target.type.kind) {
- case GDScriptDataType::BUILTIN: {
- if (p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) {
- append(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY, 2);
- append(p_target);
- append(p_source);
- } else {
- append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2);
- append(p_target);
- append(p_source);
- append(p_target.type.builtin_type);
- }
- } break;
- case GDScriptDataType::NATIVE: {
- int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_target.type.native_type];
- Variant nc = GDScriptLanguage::get_singleton()->get_global_array()[class_idx];
- class_idx = get_constant_pos(nc) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS);
- append(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE, 3);
- append(p_target);
- append(p_source);
- append(class_idx);
- } break;
- case GDScriptDataType::SCRIPT:
- case GDScriptDataType::GDSCRIPT: {
- Variant script = p_target.type.script_type;
- int idx = get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS);
-
- append(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT, 3);
+void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_target, const Address &p_source) {
+ switch (p_target.type.kind) {
+ case GDScriptDataType::BUILTIN: {
+ if (p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) {
+ append(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY, 2);
append(p_target);
append(p_source);
- append(idx);
- } break;
- default: {
- ERR_PRINT("Compiler bug: unresolved assign.");
-
- // Shouldn't get here, but fail-safe to a regular assignment
- append(GDScriptFunction::OPCODE_ASSIGN, 2);
+ } else {
+ append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2);
append(p_target);
append(p_source);
+ append(p_target.type.builtin_type);
}
- }
- } else {
- if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) {
- append(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY, 2);
+ } break;
+ case GDScriptDataType::NATIVE: {
+ int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_target.type.native_type];
+ Variant nc = GDScriptLanguage::get_singleton()->get_global_array()[class_idx];
+ class_idx = get_constant_pos(nc) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS);
+ append(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE, 3);
append(p_target);
append(p_source);
- } else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) {
- // Need conversion..
- append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2);
+ append(class_idx);
+ } break;
+ case GDScriptDataType::SCRIPT:
+ case GDScriptDataType::GDSCRIPT: {
+ Variant script = p_target.type.script_type;
+ int idx = get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS);
+
+ append(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT, 3);
append(p_target);
append(p_source);
- append(p_target.type.builtin_type);
- } else {
- // Either untyped assignment or already type-checked by the parser
+ append(idx);
+ } break;
+ default: {
+ ERR_PRINT("Compiler bug: unresolved assign.");
+
+ // Shouldn't get here, but fail-safe to a regular assignment
append(GDScriptFunction::OPCODE_ASSIGN, 2);
append(p_target);
append(p_source);
@@ -653,6 +823,24 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr
}
}
+void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Address &p_source) {
+ if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) {
+ append(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY, 2);
+ append(p_target);
+ append(p_source);
+ } else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) {
+ // Need conversion.
+ append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2);
+ append(p_target);
+ append(p_source);
+ append(p_target.type.builtin_type);
+ } else {
+ append(GDScriptFunction::OPCODE_ASSIGN, 2);
+ append(p_target);
+ append(p_source);
+ }
+}
+
void GDScriptByteCodeGenerator::write_assign_true(const Address &p_target) {
append(GDScriptFunction::OPCODE_ASSIGN_TRUE, 1);
append(p_target);
@@ -806,6 +994,14 @@ void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target,
return;
}
+ if (p_target.mode == Address::TEMPORARY) {
+ Variant::Type result_type = Variant::get_builtin_method_return_type(p_type, p_method);
+ Variant::Type temp_type = temporaries[p_target.address].type;
+ if (result_type != temp_type) {
+ write_type_adjust(p_target, result_type);
+ }
+ }
+
append(GDScriptFunction::OPCODE_CALL_BUILTIN_TYPE_VALIDATED, 2 + p_arguments.size());
for (int i = 0; i < p_arguments.size(); i++) {
@@ -817,6 +1013,56 @@ void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target,
append(Variant::get_validated_builtin_method(p_type, p_method));
}
+void GDScriptByteCodeGenerator::write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) {
+ bool is_validated = false;
+
+ // Check if all types are correct.
+ if (Variant::is_builtin_method_vararg(p_type, p_method)) {
+ is_validated = true; // Vararg works fine with any argument, since they can be any type.
+ } else if (p_arguments.size() == Variant::get_builtin_method_argument_count(p_type, p_method)) {
+ bool all_types_exact = true;
+ for (int i = 0; i < p_arguments.size(); i++) {
+ if (!IS_BUILTIN_TYPE(p_arguments[i], Variant::get_builtin_method_argument_type(p_type, p_method, i))) {
+ all_types_exact = false;
+ break;
+ }
+ }
+
+ is_validated = all_types_exact;
+ }
+
+ if (!is_validated) {
+ // Perform regular call.
+ append(GDScriptFunction::OPCODE_CALL_BUILTIN_STATIC, p_arguments.size() + 1);
+ for (int i = 0; i < p_arguments.size(); i++) {
+ append(p_arguments[i]);
+ }
+ append(p_target);
+ append(p_type);
+ append(p_method);
+ append(p_arguments.size());
+ return;
+ }
+
+ if (p_target.mode == Address::TEMPORARY) {
+ Variant::Type result_type = Variant::get_builtin_method_return_type(p_type, p_method);
+ Variant::Type temp_type = temporaries[p_target.address].type;
+ if (result_type != temp_type) {
+ write_type_adjust(p_target, result_type);
+ }
+ }
+
+ append(GDScriptFunction::OPCODE_CALL_BUILTIN_TYPE_VALIDATED, 2 + p_arguments.size());
+
+ for (int i = 0; i < p_arguments.size(); i++) {
+ append(p_arguments[i]);
+ }
+ append(Address()); // No base since it's static.
+ append(p_target);
+ append(p_arguments.size());
+ append(Variant::get_validated_builtin_method(p_type, p_method));
+}
+
void GDScriptByteCodeGenerator::write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) {
append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL_METHOD_BIND : GDScriptFunction::OPCODE_CALL_METHOD_BIND_RET, 2 + p_arguments.size());
for (int i = 0; i < p_arguments.size(); i++) {
@@ -930,6 +1176,17 @@ void GDScriptByteCodeGenerator::write_call_script_function(const Address &p_targ
append(p_function_name);
}
+void GDScriptByteCodeGenerator::write_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures) {
+ append(GDScriptFunction::OPCODE_CREATE_LAMBDA, 1 + p_captures.size());
+ for (int i = 0; i < p_captures.size(); i++) {
+ append(p_captures[i]);
+ }
+
+ append(p_target);
+ append(p_captures.size());
+ append(p_function);
+}
+
void GDScriptByteCodeGenerator::write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) {
// Try to find an appropriate constructor.
bool all_have_type = true;
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index 4b196ed420..b1f3cd5fb3 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -37,6 +37,17 @@
#include "gdscript_utility_functions.h"
class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
+ struct StackSlot {
+ Variant::Type type = Variant::NIL;
+ Vector<int> bytecode_indices;
+
+ StackSlot() = default;
+ StackSlot(Variant::Type p_type) :
+ type(p_type) {}
+ };
+
+ const static int RESERVED_STACK = 3; // For self, class, and nil.
+
bool ended = false;
GDScriptFunction *function = nullptr;
bool debug_stack = false;
@@ -47,15 +58,17 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
List<int> stack_identifiers_counts;
Map<StringName, int> local_constants;
+ Vector<StackSlot> locals;
+ Vector<StackSlot> temporaries;
+ List<int> used_temporaries;
+ Map<Variant::Type, List<int>> temporaries_pool;
+
List<GDScriptFunction::StackDebug> stack_debug;
List<Map<StringName, int>> block_identifier_stack;
Map<StringName, int> block_identifiers;
- int current_stack_size = 3; // First 3 spots are reserved for self, class, and nil.
- int current_temporaries = 0;
- int current_locals = 0;
+ int max_locals = 0;
int current_line = 0;
- int stack_max = 3;
int instr_args_max = 0;
int ptrcall_max = 0;
@@ -80,6 +93,7 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
Map<Variant::ValidatedUtilityFunction, int> utilities_map;
Map<GDScriptUtilityFunctions::FunctionPtr, int> gds_utilities_map;
Map<MethodBind *, int> method_bind_map;
+ Map<GDScriptFunction *, int> lambdas_map;
// Lists since these can be nested.
List<int> if_jmp_addrs;
@@ -102,7 +116,9 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
List<List<int>> match_continues_to_patch;
void add_stack_identifier(const StringName &p_id, int p_stackpos) {
- current_locals++;
+ if (locals.size() > max_locals) {
+ max_locals = locals.size();
+ }
stack_identifiers[p_id] = p_stackpos;
if (debug_stack) {
block_identifiers[p_id] = p_stackpos;
@@ -116,7 +132,7 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
}
void push_stack_identifiers() {
- stack_identifiers_counts.push_back(current_locals);
+ stack_identifiers_counts.push_back(locals.size());
stack_id_stack.push_back(stack_identifiers);
if (debug_stack) {
Map<StringName, int> block_ids(block_identifiers);
@@ -126,17 +142,16 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
}
void pop_stack_identifiers() {
- current_locals = stack_identifiers_counts.back()->get();
+ int current_locals = stack_identifiers_counts.back()->get();
stack_identifiers_counts.pop_back();
stack_identifiers = stack_id_stack.back()->get();
stack_id_stack.pop_back();
#ifdef DEBUG_ENABLED
- if (current_temporaries != 0) {
- ERR_PRINT("Leaving block with non-zero temporary variables: " + itos(current_temporaries));
+ if (!used_temporaries.is_empty()) {
+ ERR_PRINT("Leaving block with non-zero temporary variables: " + itos(used_temporaries.size()));
}
#endif
- current_stack_size = current_locals + 3; // Keep the 3 reserved slots for self, class, and nil.
-
+ locals.resize(current_locals);
if (debug_stack) {
for (Map<StringName, int>::Element *E = block_identifiers.front(); E; E = E->next()) {
GDScriptFunction::StackDebug sd;
@@ -279,16 +294,13 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
return pos;
}
- void alloc_stack(int p_level) {
- if (p_level >= stack_max) {
- stack_max = p_level + 1;
+ int get_lambda_function_pos(GDScriptFunction *p_lambda_function) {
+ if (lambdas_map.has(p_lambda_function)) {
+ return lambdas_map[p_lambda_function];
}
- }
-
- int increase_stack() {
- int top = current_stack_size++;
- alloc_stack(current_stack_size);
- return top;
+ int pos = lambdas_map.size();
+ lambdas_map[p_lambda_function] = pos;
+ return pos;
}
void alloc_ptrcall(int p_params) {
@@ -308,9 +320,11 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
case Address::CONSTANT:
return p_address.address | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS);
case Address::LOCAL_VARIABLE:
- case Address::TEMPORARY:
case Address::FUNCTION_PARAMETER:
return p_address.address | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
+ case Address::TEMPORARY:
+ temporaries.write[p_address.address].bytecode_indices.push_back(opcodes.size());
+ return -1;
case Address::NIL:
return GDScriptFunction::ADDR_NIL;
}
@@ -382,6 +396,10 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
opcodes.push_back(get_method_bind_pos(p_method));
}
+ void append(GDScriptFunction *p_lambda_function) {
+ opcodes.push_back(get_lambda_function_pos(p_lambda_function));
+ }
+
void patch_jump(int p_address) {
opcodes.write[p_address] = opcodes.size();
}
@@ -392,7 +410,7 @@ public:
virtual uint32_t add_local_constant(const StringName &p_name, const Variant &p_constant) override;
virtual uint32_t add_or_get_constant(const Variant &p_constant) override;
virtual uint32_t add_or_get_name(const StringName &p_name) override;
- virtual uint32_t add_temporary() override;
+ virtual uint32_t add_temporary(const GDScriptDataType &p_type) override;
virtual void pop_temporary() override;
virtual void start_parameters() override;
@@ -409,6 +427,7 @@ public:
#endif
virtual void set_initial_line(int p_line) override;
+ virtual void write_type_adjust(const Address &p_target, Variant::Type p_new_type) override;
virtual void write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) override;
virtual void write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) override;
virtual void write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) override;
@@ -431,6 +450,7 @@ public:
virtual void write_set_member(const Address &p_value, const StringName &p_name) override;
virtual void write_get_member(const Address &p_target, const StringName &p_name) override;
virtual void write_assign(const Address &p_target, const Address &p_source) override;
+ virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) override;
virtual void write_assign_true(const Address &p_target) override;
virtual void write_assign_false(const Address &p_target) override;
virtual void write_assign_default_parameter(const Address &p_dst, const Address &p_src) override;
@@ -442,11 +462,13 @@ public:
virtual void write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) override;
virtual void write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) override;
virtual void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override;
+ virtual void write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override;
virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override;
virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, 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_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_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_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) override;
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index 113d36be98..601cdb4080 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -153,9 +153,9 @@ String GDScriptCache::get_source_code(const String &p_path) {
ERR_FAIL_COND_V(err, "");
}
- int len = f->get_len();
+ uint64_t len = f->get_length();
source_file.resize(len + 1);
- int r = f->get_buffer(source_file.ptrw(), len);
+ uint64_t r = f->get_buffer(source_file.ptrw(), len);
f->close();
ERR_FAIL_COND_V(r != len, "");
source_file.write[len] = 0;
diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h
index cce4e856c7..cac6544f03 100644
--- a/modules/gdscript/gdscript_codegen.h
+++ b/modules/gdscript/gdscript_codegen.h
@@ -71,7 +71,7 @@ public:
virtual uint32_t add_local_constant(const StringName &p_name, const Variant &p_constant) = 0;
virtual uint32_t add_or_get_constant(const Variant &p_constant) = 0;
virtual uint32_t add_or_get_name(const StringName &p_name) = 0;
- virtual uint32_t add_temporary() = 0;
+ virtual uint32_t add_temporary(const GDScriptDataType &p_type) = 0;
virtual void pop_temporary() = 0;
virtual void start_parameters() = 0;
@@ -80,9 +80,6 @@ public:
virtual void start_block() = 0;
virtual void end_block() = 0;
- // virtual int get_max_stack_level() = 0;
- // virtual int get_max_function_arguments() = 0;
-
virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCMode p_rpc_mode, const GDScriptDataType &p_return_type) = 0;
virtual GDScriptFunction *write_end() = 0;
@@ -91,9 +88,7 @@ public:
#endif
virtual void set_initial_line(int p_line) = 0;
- // virtual void alloc_stack(int p_level) = 0; // Is this needed?
- // virtual void alloc_call(int p_arg_count) = 0; // This might be automatic from other functions.
-
+ virtual void write_type_adjust(const Address &p_target, Variant::Type p_new_type) = 0;
virtual void write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) = 0;
virtual void write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) = 0;
virtual void write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) = 0;
@@ -116,6 +111,7 @@ public:
virtual void write_set_member(const Address &p_value, const StringName &p_name) = 0;
virtual void write_get_member(const Address &p_target, const StringName &p_name) = 0;
virtual void write_assign(const Address &p_target, const Address &p_source) = 0;
+ virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) = 0;
virtual void write_assign_true(const Address &p_target) = 0;
virtual void write_assign_false(const Address &p_target) = 0;
virtual void write_assign_default_parameter(const Address &dst, const Address &src) = 0;
@@ -127,18 +123,19 @@ public:
virtual void write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) = 0;
virtual void write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) = 0;
virtual void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) = 0;
+ virtual void write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) = 0;
virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0;
virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, 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_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_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_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) = 0;
virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) = 0;
virtual void write_await(const Address &p_target, const Address &p_operand) = 0;
virtual void write_if(const Address &p_condition) = 0;
- // virtual void write_elseif(const Address &p_condition) = 0; This kind of makes things more difficult for no real benefit.
virtual void write_else() = 0;
virtual void write_endif() = 0;
virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) = 0;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index abbca899bd..c7ca9449c2 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -427,8 +427,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
}
break;
case GDScriptParser::DictionaryNode::LUA_TABLE:
- // Lua-style: key is an identifier interpreted as string.
- String key = static_cast<const GDScriptParser::IdentifierNode *>(dn->elements[i].key)->name;
+ // Lua-style: key is an identifier interpreted as StringName.
+ StringName key = static_cast<const GDScriptParser::IdentifierNode *>(dn->elements[i].key)->name;
element = codegen.add_constant(key);
break;
}
@@ -537,39 +537,44 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(call->callee);
if (subscript->is_attribute) {
- GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base);
- if (r_error) {
- return GDScriptCodeGenerator::Address();
- }
- if (within_await) {
- gen->write_call_async(result, base, call->function_name, arguments);
- } else if (base.type.has_type && base.type.kind != GDScriptDataType::BUILTIN) {
- // Native method, use faster path.
- StringName class_name;
- if (base.type.kind == GDScriptDataType::NATIVE) {
- class_name = base.type.native_type;
- } else {
- class_name = base.type.native_type == StringName() ? base.type.script_type->get_instance_base_type() : base.type.native_type;
+ // May be static built-in method call.
+ if (!call->is_super && subscript->base->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(subscript->base)->name) < Variant::VARIANT_MAX) {
+ gen->write_call_builtin_type_static(result, GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(subscript->base)->name), subscript->attribute->name, arguments);
+ } else {
+ GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
}
- if (ClassDB::class_exists(class_name) && ClassDB::has_method(class_name, call->function_name)) {
- MethodBind *method = ClassDB::get_method(class_name, call->function_name);
- if (_have_exact_arguments(method, arguments)) {
- // Exact arguments, use ptrcall.
- gen->write_call_ptrcall(result, base, method, arguments);
+ if (within_await) {
+ gen->write_call_async(result, base, call->function_name, arguments);
+ } else if (base.type.has_type && base.type.kind != GDScriptDataType::BUILTIN) {
+ // Native method, use faster path.
+ StringName class_name;
+ if (base.type.kind == GDScriptDataType::NATIVE) {
+ class_name = base.type.native_type;
} else {
- // Not exact arguments, but still can use method bind call.
- gen->write_call_method_bind(result, base, method, arguments);
+ class_name = base.type.native_type == StringName() ? base.type.script_type->get_instance_base_type() : base.type.native_type;
}
+ if (ClassDB::class_exists(class_name) && ClassDB::has_method(class_name, call->function_name)) {
+ MethodBind *method = ClassDB::get_method(class_name, call->function_name);
+ if (_have_exact_arguments(method, arguments)) {
+ // Exact arguments, use ptrcall.
+ gen->write_call_ptrcall(result, base, method, arguments);
+ } else {
+ // Not exact arguments, but still can use method bind call.
+ gen->write_call_method_bind(result, base, method, arguments);
+ }
+ } else {
+ gen->write_call(result, base, call->function_name, arguments);
+ }
+ } else if (base.type.has_type && base.type.kind == GDScriptDataType::BUILTIN) {
+ gen->write_call_builtin_type(result, base, base.type.builtin_type, call->function_name, arguments);
} else {
gen->write_call(result, base, call->function_name, arguments);
}
- } else if (base.type.has_type && base.type.kind == GDScriptDataType::BUILTIN) {
- gen->write_call_builtin_type(result, base, base.type.builtin_type, call->function_name, arguments);
- } else {
- gen->write_call(result, base, call->function_name, arguments);
- }
- if (base.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
- gen->pop_temporary();
+ if (base.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
}
} else {
_set_error("Cannot call something that isn't a function.", call->callee);
@@ -680,9 +685,9 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
name = subscript->attribute->name;
named = true;
} else {
- if (subscript->index->type == GDScriptParser::Node::LITERAL && static_cast<const GDScriptParser::LiteralNode *>(subscript->index)->value.get_type() == Variant::STRING) {
+ if (subscript->index->is_constant && subscript->index->reduced_value.get_type() == Variant::STRING_NAME) {
// Also, somehow, named (speed up anyway).
- name = static_cast<const GDScriptParser::LiteralNode *>(subscript->index)->value;
+ name = subscript->index->reduced_value;
named = true;
} else {
// Regular indexing.
@@ -711,7 +716,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
case GDScriptParser::Node::UNARY_OPERATOR: {
const GDScriptParser::UnaryOpNode *unary = static_cast<const GDScriptParser::UnaryOpNode *>(p_expression);
- GDScriptCodeGenerator::Address result = codegen.add_temporary();
+ GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(unary->get_datatype()));
GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, unary->operand);
if (r_error) {
@@ -729,7 +734,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
case GDScriptParser::Node::BINARY_OPERATOR: {
const GDScriptParser::BinaryOpNode *binary = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression);
- GDScriptCodeGenerator::Address result = codegen.add_temporary();
+ GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(binary->get_datatype()));
switch (binary->operation) {
case GDScriptParser::BinaryOpNode::OP_LOGIC_AND: {
@@ -966,6 +971,9 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
} else {
gen->write_set(prev_base, key, assigned);
}
+ if (key.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
gen->pop_temporary();
}
@@ -1076,7 +1084,11 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), setter_function, args);
} else {
// Just assign.
- gen->write_assign(target, op_result);
+ if (assignment->use_conversion_assign) {
+ gen->write_assign_with_conversion(target, op_result);
+ } else {
+ gen->write_assign(target, op_result);
+ }
}
if (op_result.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
@@ -1091,6 +1103,34 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
}
return GDScriptCodeGenerator::Address(); // Assignment does not return a value.
} break;
+ case GDScriptParser::Node::LAMBDA: {
+ const GDScriptParser::LambdaNode *lambda = static_cast<const GDScriptParser::LambdaNode *>(p_expression);
+ GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(lambda->get_datatype()));
+
+ Vector<GDScriptCodeGenerator::Address> captures;
+ captures.resize(lambda->captures.size());
+ for (int i = 0; i < lambda->captures.size(); i++) {
+ captures.write[i] = _parse_expression(codegen, r_error, lambda->captures[i]);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
+ }
+ }
+
+ GDScriptFunction *function = _parse_function(r_error, codegen.script, codegen.class_node, lambda->function, false, true);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
+ }
+
+ gen->write_lambda(result, function, captures);
+
+ for (int i = 0; i < captures.size(); i++) {
+ if (captures[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
+ }
+
+ return result;
+ } break;
default: {
ERR_FAIL_V_MSG(GDScriptCodeGenerator::Address(), "Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); // Unreachable code.
} break;
@@ -1756,7 +1796,11 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
if (error) {
return error;
}
- gen->write_assign(local, src_address);
+ if (lv->use_conversion_assign) {
+ gen->write_assign_with_conversion(local, src_address);
+ } else {
+ gen->write_assign(local, src_address);
+ }
if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
codegen.generator->pop_temporary();
}
@@ -1804,8 +1848,8 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
return OK;
}
-Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready) {
- Error error = OK;
+GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready, bool p_for_lambda) {
+ r_error = OK;
CodeGen codegen;
codegen.generator = memnew(GDScriptByteCodeGenerator);
@@ -1822,7 +1866,11 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
return_type.builtin_type = Variant::NIL;
if (p_func) {
- func_name = p_func->identifier->name;
+ if (p_func->identifier) {
+ func_name = p_func->identifier->name;
+ } else {
+ func_name = "<anonymous lambda>";
+ }
is_static = p_func->is_static;
rpc_mode = p_func->rpc_mode;
return_type = _gdtype_from_datatype(p_func->get_datatype(), p_script);
@@ -1853,11 +1901,11 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
}
// Parse initializer if applies.
- bool is_implicit_initializer = !p_for_ready && !p_func;
- bool is_initializer = p_func && String(p_func->identifier->name) == GDScriptLanguage::get_singleton()->strings._init;
- bool is_for_ready = p_for_ready || (p_func && String(p_func->identifier->name) == "_ready");
+ bool is_implicit_initializer = !p_for_ready && !p_func && !p_for_lambda;
+ bool is_initializer = p_func && !p_for_lambda && String(p_func->identifier->name) == GDScriptLanguage::get_singleton()->strings._init;
+ bool is_for_ready = p_for_ready || (p_func && !p_for_lambda && String(p_func->identifier->name) == "_ready");
- if (is_implicit_initializer || is_for_ready) {
+ if (!p_for_lambda && (is_implicit_initializer || is_for_ready)) {
// Initialize class fields.
for (int i = 0; i < p_class->members.size(); i++) {
if (p_class->members[i].type != GDScriptParser::ClassNode::Member::VARIABLE) {
@@ -1884,13 +1932,17 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
codegen.generator->write_construct_array(dst_address, Vector<GDScriptCodeGenerator::Address>());
}
}
- GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, error, field->initializer, false, true);
- if (error) {
+ GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, r_error, field->initializer, false, true);
+ if (r_error) {
memdelete(codegen.generator);
- return error;
+ return nullptr;
}
- codegen.generator->write_assign(dst_address, src_address);
+ if (field->use_conversion_assign) {
+ codegen.generator->write_assign_with_conversion(dst_address, src_address);
+ } else {
+ codegen.generator->write_assign(dst_address, src_address);
+ }
if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
codegen.generator->pop_temporary();
}
@@ -1914,10 +1966,10 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
codegen.generator->start_parameters();
for (int i = p_func->parameters.size() - optional_parameters; i < p_func->parameters.size(); i++) {
const GDScriptParser::ParameterNode *parameter = p_func->parameters[i];
- GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, error, parameter->default_value, true);
- if (error) {
+ GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, r_error, parameter->default_value, true);
+ if (r_error) {
memdelete(codegen.generator);
- return error;
+ return nullptr;
}
GDScriptCodeGenerator::Address dst_addr = codegen.parameters[parameter->identifier->name];
codegen.generator->write_assign_default_parameter(dst_addr, src_addr);
@@ -1928,10 +1980,10 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
codegen.generator->end_parameters();
}
- Error err = _parse_block(codegen, p_func->body);
- if (err) {
+ r_error = _parse_block(codegen, p_func->body);
+ if (r_error) {
memdelete(codegen.generator);
- return err;
+ return nullptr;
}
}
@@ -1957,6 +2009,10 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
signature += "::" + String(func_name);
}
+ if (p_for_lambda) {
+ signature += "(lambda)";
+ }
+
codegen.generator->set_signature(signature);
}
#endif
@@ -1964,8 +2020,10 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
if (p_func) {
codegen.generator->set_initial_line(p_func->start_line);
#ifdef TOOLS_ENABLED
- p_script->member_lines[func_name] = p_func->start_line;
- p_script->doc_functions[func_name] = p_func->doc_description;
+ if (!p_for_lambda) {
+ p_script->member_lines[func_name] = p_func->start_line;
+ p_script->doc_functions[func_name] = p_func->doc_description;
+ }
#endif
} else {
codegen.generator->set_initial_line(0);
@@ -1994,11 +2052,13 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
#endif
}
- p_script->member_functions[func_name] = gd_function;
+ if (!p_for_lambda) {
+ p_script->member_functions[func_name] = gd_function;
+ }
memdelete(codegen.generator);
- return OK;
+ return gd_function;
}
Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter) {
@@ -2163,7 +2223,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
if (err) {
return err;
}
- if (base.is_null() && !base->is_valid()) {
+ if (base.is_null() || !base->is_valid()) {
return ERR_COMPILATION_FAILED;
}
}
@@ -2391,7 +2451,8 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa
if (!has_ready && function->identifier->name == "_ready") {
has_ready = true;
}
- Error err = _parse_function(p_script, p_class, function);
+ Error err = OK;
+ _parse_function(err, p_script, p_class, function);
if (err) {
return err;
}
@@ -2416,7 +2477,8 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa
{
// Create an implicit constructor in any case.
- Error err = _parse_function(p_script, p_class, nullptr);
+ Error err = OK;
+ _parse_function(err, p_script, p_class, nullptr);
if (err) {
return err;
}
@@ -2424,7 +2486,8 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa
if (!has_ready && p_class->onready_used) {
//create a _ready constructor
- Error err = _parse_function(p_script, p_class, nullptr, true);
+ Error err = OK;
+ _parse_function(err, p_script, p_class, nullptr, true);
if (err) {
return err;
}
diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h
index 1b0beec0d4..7d5bee93ac 100644
--- a/modules/gdscript/gdscript_compiler.h
+++ b/modules/gdscript/gdscript_compiler.h
@@ -66,7 +66,7 @@ class GDScriptCompiler {
}
GDScriptCodeGenerator::Address add_temporary(const GDScriptDataType &p_type = GDScriptDataType()) {
- uint32_t addr = generator->add_temporary();
+ uint32_t addr = generator->add_temporary(p_type);
return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::TEMPORARY, addr, p_type);
}
@@ -128,7 +128,7 @@ class GDScriptCompiler {
GDScriptCodeGenerator::Address _parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested);
void _add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block);
Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true);
- Error _parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false);
+ GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = false);
Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter);
Error _parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
Error _parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
index 74da0ee232..eee713aa45 100644
--- a/modules/gdscript/gdscript_disassembler.cpp
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -397,7 +397,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += DADDR(1 + argc);
text += " = ";
- text += "<unkown type>(";
+ text += "<unknown type>(";
for (int i = 0; i < argc; i++) {
if (i > 0) {
text += ", ";
@@ -542,6 +542,28 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr = 5 + argc;
} break;
+ case OPCODE_CALL_BUILTIN_STATIC: {
+ Variant::Type type = (Variant::Type)_code_ptr[ip + 1 + instr_var_args];
+ int argc = _code_ptr[ip + 3 + instr_var_args];
+
+ text += "call built-in method static ";
+ text += DADDR(1 + argc);
+ text += " = ";
+ text += Variant::get_type_name(type);
+ text += ".";
+ text += _global_names_ptr[_code_ptr[ip + 2 + instr_var_args]].operator String();
+ text += "(";
+
+ for (int i = 0; i < argc; i++) {
+ if (i > 0) {
+ text += ", ";
+ }
+ text += DADDR(1 + i);
+ }
+ text += ")";
+
+ incr += 5 + argc;
+ } break;
case OPCODE_CALL_PTRCALL_NO_RETURN: {
text += "call-ptrcall (no return) ";
@@ -666,7 +688,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
int argc = _code_ptr[ip + 1 + instr_var_args];
text += DADDR(1 + argc) + " = ";
- text += "<unkown function>";
+ text += "<unknown function>";
text += "(";
for (int i = 0; i < argc; i++) {
@@ -721,7 +743,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += "await ";
text += DADDR(1);
- incr += 2;
+ incr = 2;
} break;
case OPCODE_AWAIT_RESUME: {
text += "await resume ";
@@ -729,6 +751,25 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr = 2;
} break;
+ case OPCODE_CREATE_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 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: {
text += "jump ";
text += itos(_code_ptr[ip + 1]);
@@ -894,6 +935,51 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 2;
} break;
+
+#define DISASSEMBLE_TYPE_ADJUST(m_v_type) \
+ case OPCODE_TYPE_ADJUST_##m_v_type: { \
+ text += "type adjust ("; \
+ text += #m_v_type; \
+ text += ") "; \
+ text += DADDR(1); \
+ incr += 2; \
+ } break
+
+ DISASSEMBLE_TYPE_ADJUST(BOOL);
+ DISASSEMBLE_TYPE_ADJUST(INT);
+ DISASSEMBLE_TYPE_ADJUST(FLOAT);
+ DISASSEMBLE_TYPE_ADJUST(STRING);
+ DISASSEMBLE_TYPE_ADJUST(VECTOR2);
+ DISASSEMBLE_TYPE_ADJUST(VECTOR2I);
+ DISASSEMBLE_TYPE_ADJUST(RECT2);
+ DISASSEMBLE_TYPE_ADJUST(RECT2I);
+ DISASSEMBLE_TYPE_ADJUST(VECTOR3);
+ DISASSEMBLE_TYPE_ADJUST(VECTOR3I);
+ DISASSEMBLE_TYPE_ADJUST(TRANSFORM2D);
+ DISASSEMBLE_TYPE_ADJUST(PLANE);
+ DISASSEMBLE_TYPE_ADJUST(QUAT);
+ DISASSEMBLE_TYPE_ADJUST(AABB);
+ DISASSEMBLE_TYPE_ADJUST(BASIS);
+ DISASSEMBLE_TYPE_ADJUST(TRANSFORM);
+ DISASSEMBLE_TYPE_ADJUST(COLOR);
+ DISASSEMBLE_TYPE_ADJUST(STRING_NAME);
+ DISASSEMBLE_TYPE_ADJUST(NODE_PATH);
+ DISASSEMBLE_TYPE_ADJUST(RID);
+ DISASSEMBLE_TYPE_ADJUST(OBJECT);
+ DISASSEMBLE_TYPE_ADJUST(CALLABLE);
+ DISASSEMBLE_TYPE_ADJUST(SIGNAL);
+ DISASSEMBLE_TYPE_ADJUST(DICTIONARY);
+ DISASSEMBLE_TYPE_ADJUST(ARRAY);
+ DISASSEMBLE_TYPE_ADJUST(PACKED_BYTE_ARRAY);
+ DISASSEMBLE_TYPE_ADJUST(PACKED_INT32_ARRAY);
+ DISASSEMBLE_TYPE_ADJUST(PACKED_INT64_ARRAY);
+ DISASSEMBLE_TYPE_ADJUST(PACKED_FLOAT32_ARRAY);
+ DISASSEMBLE_TYPE_ADJUST(PACKED_FLOAT64_ARRAY);
+ DISASSEMBLE_TYPE_ADJUST(PACKED_STRING_ARRAY);
+ DISASSEMBLE_TYPE_ADJUST(PACKED_VECTOR2_ARRAY);
+ DISASSEMBLE_TYPE_ADJUST(PACKED_VECTOR3_ARRAY);
+ DISASSEMBLE_TYPE_ADJUST(PACKED_COLOR_ARRAY);
+
case OPCODE_ASSERT: {
text += "assert (";
text += DADDR(1);
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index ae3b16a9d7..504c7414f6 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -738,7 +738,13 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio
static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, Map<String, ScriptCodeCompletionOption> &r_result) {
for (int i = 0; i < p_suite->locals.size(); i++) {
- ScriptCodeCompletionOption option(p_suite->locals[i].name, ScriptCodeCompletionOption::KIND_VARIABLE);
+ ScriptCodeCompletionOption option;
+ if (p_suite->locals[i].type == GDScriptParser::SuiteNode::Local::CONSTANT) {
+ option = ScriptCodeCompletionOption(p_suite->locals[i].name, ScriptCodeCompletionOption::KIND_CONSTANT);
+ option.default_value = p_suite->locals[i].constant->initializer->reduced_value;
+ } else {
+ option = ScriptCodeCompletionOption(p_suite->locals[i].name, ScriptCodeCompletionOption::KIND_VARIABLE);
+ }
r_result.insert(option.display, option);
}
if (p_suite->parent_block) {
@@ -2867,7 +2873,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
StringName parent = ClassDB::get_parent_class(class_name);
if (parent != StringName()) {
if (String(parent).begins_with("_")) {
- base_type.native_type = String(parent).right(1);
+ base_type.native_type = String(parent).substr(1);
} else {
base_type.native_type = parent;
}
@@ -2993,6 +2999,7 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
is_function = true;
[[fallthrough]];
}
+ case GDScriptParser::COMPLETION_CALL_ARGUMENTS:
case GDScriptParser::COMPLETION_IDENTIFIER: {
GDScriptParser::DataType base_type;
if (context.current_class) {
@@ -3060,7 +3067,7 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
// proxy class remove the underscore.
if (r_result.class_name.begins_with("_")) {
- r_result.class_name = r_result.class_name.right(1);
+ r_result.class_name = r_result.class_name.substr(1);
}
return OK;
}
@@ -3070,7 +3077,7 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
// We cannot determine the exact nature of the identifier here
// Otherwise these codes would work
StringName enumName = ClassDB::get_integer_constant_enum("@GlobalScope", p_symbol, true);
- if (enumName != NULL) {
+ if (enumName != nullptr) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_ENUM;
r_result.class_name = "@GlobalScope";
r_result.class_member = enumName;
diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp
index 7b37aa40a2..78399114a5 100644
--- a/modules/gdscript/gdscript_function.cpp
+++ b/modules/gdscript/gdscript_function.cpp
@@ -150,6 +150,10 @@ GDScriptFunction::GDScriptFunction() {
}
GDScriptFunction::~GDScriptFunction() {
+ for (int i = 0; i < lambdas.size(); i++) {
+ memdelete(lambdas[i]);
+ }
+
#ifdef DEBUG_ENABLED
MutexLock lock(GDScriptLanguage::get_singleton()->lock);
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index 414dfab2e7..26b8809337 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -263,6 +263,7 @@ public:
OPCODE_CALL_SELF_BASE,
OPCODE_CALL_METHOD_BIND,
OPCODE_CALL_METHOD_BIND_RET,
+ OPCODE_CALL_BUILTIN_STATIC,
// ptrcall have one instruction per return type.
OPCODE_CALL_PTRCALL_NO_RETURN,
OPCODE_CALL_PTRCALL_BOOL,
@@ -301,6 +302,7 @@ public:
OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY,
OPCODE_AWAIT,
OPCODE_AWAIT_RESUME,
+ OPCODE_CREATE_LAMBDA,
OPCODE_JUMP,
OPCODE_JUMP_IF,
OPCODE_JUMP_IF_NOT,
@@ -351,6 +353,40 @@ public:
OPCODE_ITERATE_PACKED_COLOR_ARRAY,
OPCODE_ITERATE_OBJECT,
OPCODE_STORE_NAMED_GLOBAL,
+ OPCODE_TYPE_ADJUST_BOOL,
+ OPCODE_TYPE_ADJUST_INT,
+ OPCODE_TYPE_ADJUST_FLOAT,
+ OPCODE_TYPE_ADJUST_STRING,
+ OPCODE_TYPE_ADJUST_VECTOR2,
+ OPCODE_TYPE_ADJUST_VECTOR2I,
+ OPCODE_TYPE_ADJUST_RECT2,
+ OPCODE_TYPE_ADJUST_RECT2I,
+ OPCODE_TYPE_ADJUST_VECTOR3,
+ OPCODE_TYPE_ADJUST_VECTOR3I,
+ OPCODE_TYPE_ADJUST_TRANSFORM2D,
+ OPCODE_TYPE_ADJUST_PLANE,
+ OPCODE_TYPE_ADJUST_QUAT,
+ OPCODE_TYPE_ADJUST_AABB,
+ OPCODE_TYPE_ADJUST_BASIS,
+ OPCODE_TYPE_ADJUST_TRANSFORM,
+ OPCODE_TYPE_ADJUST_COLOR,
+ OPCODE_TYPE_ADJUST_STRING_NAME,
+ OPCODE_TYPE_ADJUST_NODE_PATH,
+ OPCODE_TYPE_ADJUST_RID,
+ OPCODE_TYPE_ADJUST_OBJECT,
+ OPCODE_TYPE_ADJUST_CALLABLE,
+ OPCODE_TYPE_ADJUST_SIGNAL,
+ OPCODE_TYPE_ADJUST_DICTIONARY,
+ OPCODE_TYPE_ADJUST_ARRAY,
+ OPCODE_TYPE_ADJUST_PACKED_BYTE_ARRAY,
+ OPCODE_TYPE_ADJUST_PACKED_INT32_ARRAY,
+ OPCODE_TYPE_ADJUST_PACKED_INT64_ARRAY,
+ OPCODE_TYPE_ADJUST_PACKED_FLOAT32_ARRAY,
+ OPCODE_TYPE_ADJUST_PACKED_FLOAT64_ARRAY,
+ OPCODE_TYPE_ADJUST_PACKED_STRING_ARRAY,
+ OPCODE_TYPE_ADJUST_PACKED_VECTOR2_ARRAY,
+ OPCODE_TYPE_ADJUST_PACKED_VECTOR3_ARRAY,
+ OPCODE_TYPE_ADJUST_PACKED_COLOR_ARRAY,
OPCODE_ASSERT,
OPCODE_BREAKPOINT,
OPCODE_LINE,
@@ -425,6 +461,8 @@ private:
const GDScriptUtilityFunctions::FunctionPtr *_gds_utilities_ptr = nullptr;
int _methods_count = 0;
MethodBind **_methods_ptr = nullptr;
+ int _lambdas_count = 0;
+ GDScriptFunction **_lambdas_ptr = nullptr;
const int *_code_ptr = nullptr;
int _code_size = 0;
int _argument_count = 0;
@@ -454,10 +492,13 @@ private:
Vector<Variant::ValidatedUtilityFunction> utilities;
Vector<GDScriptUtilityFunctions::FunctionPtr> gds_utilities;
Vector<MethodBind *> methods;
+ Vector<GDScriptFunction *> lambdas;
Vector<int> code;
Vector<GDScriptDataType> argument_types;
GDScriptDataType return_type;
+ Map<int, Variant::Type> temporary_slots;
+
#ifdef TOOLS_ENABLED
Vector<StringName> arg_names;
Vector<Variant> default_arg_values;
diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp
new file mode 100644
index 0000000000..0bc109b6e1
--- /dev/null
+++ b/modules/gdscript/gdscript_lambda_callable.cpp
@@ -0,0 +1,95 @@
+/*************************************************************************/
+/* gdscript_lambda_callable.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "gdscript_lambda_callable.h"
+
+#include "core/templates/hashfuncs.h"
+#include "gdscript.h"
+
+bool GDScriptLambdaCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
+ // Lambda callables are only compared by reference.
+ return p_a == p_b;
+}
+
+bool GDScriptLambdaCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) {
+ // Lambda callables are only compared by reference.
+ return p_a < p_b;
+}
+
+uint32_t GDScriptLambdaCallable::hash() const {
+ return h;
+}
+
+String GDScriptLambdaCallable::get_as_text() const {
+ if (function->get_name() != StringName()) {
+ return function->get_name().operator String() + "(lambda)";
+ }
+ return "(anonymous lambda)";
+}
+
+CallableCustom::CompareEqualFunc GDScriptLambdaCallable::get_compare_equal_func() const {
+ return compare_equal;
+}
+
+CallableCustom::CompareLessFunc GDScriptLambdaCallable::get_compare_less_func() const {
+ return compare_less;
+}
+
+ObjectID GDScriptLambdaCallable::get_object() const {
+ return script->get_instance_id();
+}
+
+void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
+ 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(nullptr, args.ptrw(), args.size(), r_call_error);
+ r_call_error.argument -= captures_amount;
+ } else {
+ r_return_value = function->call(nullptr, p_arguments, p_argcount, r_call_error);
+ }
+}
+
+GDScriptLambdaCallable::GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures) {
+ script = p_script;
+ function = p_function;
+ captures = p_captures;
+
+ h = (uint32_t)hash_djb2_one_64((uint64_t)this);
+}
diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h
new file mode 100644
index 0000000000..357c845250
--- /dev/null
+++ b/modules/gdscript/gdscript_lambda_callable.h
@@ -0,0 +1,65 @@
+/*************************************************************************/
+/* gdscript_lambda_callable.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GDSCRIPT_LAMBDA_CALLABLE
+#define GDSCRIPT_LAMBDA_CALLABLE
+
+#include "core/object/reference.h"
+#include "core/templates/vector.h"
+#include "core/variant/callable.h"
+#include "core/variant/variant.h"
+
+class GDScript;
+class GDScriptFunction;
+class GDScriptInstance;
+
+class GDScriptLambdaCallable : public CallableCustom {
+ GDScriptFunction *function = nullptr;
+ Ref<GDScript> script;
+ 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;
+
+ GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures);
+ virtual ~GDScriptLambdaCallable() = default;
+};
+
+#endif // GDSCRIPT_LAMBDA_CALLABLE
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index ca8bb8fcae..b61b469fbc 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -402,6 +402,8 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
}
GDScriptTokenizer::Token GDScriptParser::advance() {
+ lambda_ended = false; // Empty marker since we're past the end in any case.
+
if (current.type == GDScriptTokenizer::Token::TK_EOF) {
ERR_FAIL_COND_V_MSG(current.type == GDScriptTokenizer::Token::TK_EOF, current, "GDScript parser bug: Trying to advance past the end of stream.");
}
@@ -428,7 +430,7 @@ bool GDScriptParser::match(GDScriptTokenizer::Token::Type p_token_type) {
return true;
}
-bool GDScriptParser::check(GDScriptTokenizer::Token::Type p_token_type) {
+bool GDScriptParser::check(GDScriptTokenizer::Token::Type p_token_type) const {
if (p_token_type == GDScriptTokenizer::Token::IDENTIFIER) {
return current.is_identifier();
}
@@ -443,7 +445,7 @@ bool GDScriptParser::consume(GDScriptTokenizer::Token::Type p_token_type, const
return false;
}
-bool GDScriptParser::is_at_end() {
+bool GDScriptParser::is_at_end() const {
return check(GDScriptTokenizer::Token::TK_EOF);
}
@@ -494,16 +496,34 @@ void GDScriptParser::pop_multiline() {
tokenizer.set_multiline_mode(multiline_stack.size() > 0 ? multiline_stack.back()->get() : false);
}
-bool GDScriptParser::is_statement_end() {
+bool GDScriptParser::is_statement_end_token() const {
return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON) || check(GDScriptTokenizer::Token::TK_EOF);
}
+bool GDScriptParser::is_statement_end() const {
+ return lambda_ended || in_lambda || is_statement_end_token();
+}
+
void GDScriptParser::end_statement(const String &p_context) {
bool found = false;
while (is_statement_end() && !is_at_end()) {
// Remove sequential newlines/semicolons.
+ if (is_statement_end_token()) {
+ // Only consume if this is an actual token.
+ advance();
+ } else if (lambda_ended) {
+ lambda_ended = false; // Consume this "token".
+ found = true;
+ break;
+ } else {
+ if (!found) {
+ lambda_ended = true; // Mark the lambda as done since we found something else to end the statement.
+ found = true;
+ }
+ break;
+ }
+
found = true;
- advance();
}
if (!found && !is_at_end()) {
push_error(vformat(R"(Expected end of statement after %s, found "%s" instead.)", p_context, current.get_name()));
@@ -1153,7 +1173,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)");
#ifdef TOOLS_ENABLED
- // Enum values documentaion.
+ // Enum values documentation.
for (int i = 0; i < enum_node->values.size(); i++) {
if (i == enum_node->values.size() - 1) {
// If close bracket is same line as last value.
@@ -1182,36 +1202,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
return enum_node;
}
-GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
- bool _static = false;
- if (previous.type == GDScriptTokenizer::Token::STATIC) {
- // TODO: Improve message if user uses "static" with "var" or "const"
- if (!consume(GDScriptTokenizer::Token::FUNC, R"(Expected "func" after "static".)")) {
- return nullptr;
- }
- _static = true;
- }
-
- FunctionNode *function = alloc_node<FunctionNode>();
- make_completion_context(COMPLETION_OVERRIDE_METHOD, function);
-
- if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) {
- return nullptr;
- }
-
- FunctionNode *previous_function = current_function;
- current_function = function;
-
- function->identifier = parse_identifier();
- function->is_static = _static;
-
- push_multiline(true);
- consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after function name.)");
-
- SuiteNode *body = alloc_node<SuiteNode>();
- SuiteNode *previous_suite = current_suite;
- current_suite = body;
-
+void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type) {
if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
bool default_used = false;
do {
@@ -1231,29 +1222,61 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
continue;
}
}
- if (function->parameters_indices.has(parameter->identifier->name)) {
- push_error(vformat(R"(Parameter with name "%s" was already declared for this function.)", parameter->identifier->name));
+ if (p_function->parameters_indices.has(parameter->identifier->name)) {
+ push_error(vformat(R"(Parameter with name "%s" was already declared for this %s.)", parameter->identifier->name, p_type));
} else {
- function->parameters_indices[parameter->identifier->name] = function->parameters.size();
- function->parameters.push_back(parameter);
- body->add_local(parameter);
+ p_function->parameters_indices[parameter->identifier->name] = p_function->parameters.size();
+ p_function->parameters.push_back(parameter);
+ p_body->add_local(parameter, current_function);
}
} while (match(GDScriptTokenizer::Token::COMMA));
}
pop_multiline();
- consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after function parameters.)*");
+ consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, vformat(R"*(Expected closing ")" after %s parameters.)*", p_type));
if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) {
- make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, function);
- function->return_type = parse_type(true);
- if (function->return_type == nullptr) {
+ make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, p_function);
+ p_function->return_type = parse_type(true);
+ if (p_function->return_type == nullptr) {
push_error(R"(Expected return type or "void" after "->".)");
}
}
// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
- consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after function declaration.)");
+ consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after %s declaration.)", p_type));
+}
+
+GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
+ bool _static = false;
+ if (previous.type == GDScriptTokenizer::Token::STATIC) {
+ // TODO: Improve message if user uses "static" with "var" or "const"
+ if (!consume(GDScriptTokenizer::Token::FUNC, R"(Expected "func" after "static".)")) {
+ return nullptr;
+ }
+ _static = true;
+ }
+
+ FunctionNode *function = alloc_node<FunctionNode>();
+ make_completion_context(COMPLETION_OVERRIDE_METHOD, function);
+
+ if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) {
+ return nullptr;
+ }
+
+ FunctionNode *previous_function = current_function;
+ current_function = function;
+
+ function->identifier = parse_identifier();
+ function->is_static = _static;
+
+ SuiteNode *body = alloc_node<SuiteNode>();
+ SuiteNode *previous_suite = current_suite;
+ current_suite = body;
+
+ push_multiline(true);
+ consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after function name.)");
+ parse_function_signature(function, body, "function");
current_suite = previous_suite;
function->body = parse_suite("function declaration", body);
@@ -1339,29 +1362,34 @@ bool GDScriptParser::register_annotation(const MethodInfo &p_info, uint32_t p_ta
return true;
}
-GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, SuiteNode *p_suite) {
+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;
- if (check(GDScriptTokenizer::Token::NEWLINE)) {
+ if (match(GDScriptTokenizer::Token::NEWLINE)) {
multiline = true;
}
if (multiline) {
- consume(GDScriptTokenizer::Token::NEWLINE, vformat(R"(Expected newline after %s.)", p_context));
-
if (!consume(GDScriptTokenizer::Token::INDENT, vformat(R"(Expected indented block after %s.)", p_context))) {
current_suite = suite->parent_block;
return suite;
}
}
+ int error_count = 0;
+
do {
Node *statement = parse_statement();
if (statement == nullptr) {
+ if (error_count++ > 100) {
+ push_error("Too many statement errors.", suite);
+ break;
+ }
continue;
}
suite->statements.push_back(statement);
@@ -1374,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: {
@@ -1389,19 +1417,29 @@ 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:
break;
}
- } while (multiline && !check(GDScriptTokenizer::Token::DEDENT) && !is_at_end());
+ } while (multiline && !check(GDScriptTokenizer::Token::DEDENT) && !lambda_ended && !is_at_end());
if (multiline) {
- consume(GDScriptTokenizer::Token::DEDENT, vformat(R"(Missing unindent at the end of %s.)", p_context));
+ if (!lambda_ended) {
+ consume(GDScriptTokenizer::Token::DEDENT, vformat(R"(Missing unindent at the end of %s.)", p_context));
+
+ } else {
+ match(GDScriptTokenizer::Token::DEDENT);
+ }
+ } else if (previous.type == GDScriptTokenizer::Token::SEMICOLON) {
+ consume(GDScriptTokenizer::Token::NEWLINE, vformat(R"(Expected newline after ";" at the end of %s.)", p_context));
}
+ if (p_for_lambda) {
+ lambda_ended = true;
+ }
current_suite = suite->parent_block;
return suite;
}
@@ -1458,6 +1496,10 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
push_error(R"(Constructor cannot return a value.)");
}
n_return->return_value = parse_expression(false);
+ } else if (in_lambda && !is_statement_end_token()) {
+ // Try to parse it anyway as this might not be the statement end in a lambda.
+ // If this fails the expression will be nullptr, but that's the same as no return, so it's fine.
+ n_return->return_value = parse_expression(false);
}
result = n_return;
@@ -1486,10 +1528,18 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
default: {
// Expression statement.
ExpressionNode *expression = parse_expression(true); // Allow assignment here.
+ bool has_ended_lambda = false;
if (expression == nullptr) {
- push_error(vformat(R"(Expected statement, found "%s" instead.)", previous.get_name()));
+ if (in_lambda) {
+ // If it's not a valid expression beginning, it might be the continuation of the outer expression where this lambda is.
+ lambda_ended = true;
+ has_ended_lambda = true;
+ } else {
+ push_error(vformat(R"(Expected statement, found "%s" instead.)", previous.get_name()));
+ }
}
end_statement("expression");
+ lambda_ended = lambda_ended || has_ended_lambda;
result = expression;
#ifdef DEBUG_ENABLED
@@ -1513,7 +1563,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
if (unreachable && result != nullptr) {
current_suite->has_unreachable_code = true;
if (current_function) {
- push_warning(result, GDScriptWarning::UNREACHABLE_CODE, current_function->identifier->name);
+ push_warning(result, GDScriptWarning::UNREACHABLE_CODE, current_function->identifier ? current_function->identifier->name : "<anonymous lambda>");
} else {
// TODO: Properties setters and getters with unreachable code are not being warned
}
@@ -1598,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;
@@ -1753,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);
}
}
@@ -1953,7 +2003,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr
// Completion can appear whenever an expression is expected.
make_completion_context(COMPLETION_IDENTIFIER, nullptr);
- GDScriptTokenizer::Token token = advance();
+ GDScriptTokenizer::Token token = current;
ParseFunction prefix_rule = get_rule(token.type)->prefix;
if (prefix_rule == nullptr) {
@@ -1961,6 +2011,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr
return nullptr;
}
+ advance(); // Only consume the token if there's a valid rule.
+
ExpressionNode *previous_operand = (this->*prefix_rule)(nullptr, p_can_assign);
while (p_precedence <= get_rule(current.type)->precedence) {
@@ -2002,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;
@@ -2055,6 +2109,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_self(ExpressionNode *p_pre
if (current_function && current_function->is_static) {
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>();
self->current_class = current_class;
return self;
@@ -2440,6 +2497,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode
push_error(R"(Expected "=" after dictionary key.)");
}
}
+ key->is_constant = true;
+ key->reduced_value = static_cast<IdentifierNode *>(key)->name;
break;
case DictionaryNode::PYTHON_DICT:
if (!match(GDScriptTokenizer::Token::COLON)) {
@@ -2486,7 +2545,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *
if (for_completion) {
bool is_builtin = false;
- if (p_previous_operand->type == Node::IDENTIFIER) {
+ if (p_previous_operand && p_previous_operand->type == Node::IDENTIFIER) {
const IdentifierNode *id = static_cast<const IdentifierNode *>(p_previous_operand);
Variant::Type builtin_type = get_builtin_type(id->name);
if (builtin_type < Variant::VARIANT_MAX) {
@@ -2673,6 +2732,65 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_
return preload;
}
+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();
+ }
+
+ bool multiline_context = multiline_stack.back()->get();
+
+ // Reset the multiline stack since we don't want the multiline mode one in the lambda body.
+ push_multiline(false);
+ if (multiline_context) {
+ tokenizer.push_expression_indented_block();
+ }
+
+ push_multiline(true); // For the parameters.
+ if (function->identifier) {
+ consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after lambda name.)");
+ } else {
+ consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after "func".)");
+ }
+
+ FunctionNode *previous_function = current_function;
+ current_function = function;
+
+ SuiteNode *body = alloc_node<SuiteNode>();
+ SuiteNode *previous_suite = current_suite;
+ current_suite = body;
+
+ parse_function_signature(function, body, "lambda");
+
+ current_suite = previous_suite;
+
+ bool previous_in_lambda = in_lambda;
+ in_lambda = true;
+
+ function->body = parse_suite("lambda declaration", body, true);
+
+ pop_multiline();
+
+ if (multiline_context) {
+ // If we're in multiline mode, we want to skip the spurious DEDENT and NEWLINE tokens.
+ while (check(GDScriptTokenizer::Token::DEDENT) || check(GDScriptTokenizer::Token::INDENT) || check(GDScriptTokenizer::Token::NEWLINE)) {
+ current = tokenizer.scan(); // Not advance() since we don't want to change the previous token.
+ }
+ tokenizer.pop_expression_indented_block();
+ }
+
+ current_function = previous_function;
+ in_lambda = previous_in_lambda;
+ lambda->function = function;
+ return lambda;
+}
+
GDScriptParser::ExpressionNode *GDScriptParser::parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign) {
// Just for better error messages.
GDScriptTokenizer::Token::Type invalid = previous.type;
@@ -3017,7 +3135,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty
{ nullptr, nullptr, PREC_NONE }, // CONST,
{ nullptr, nullptr, PREC_NONE }, // ENUM,
{ nullptr, nullptr, PREC_NONE }, // EXTENDS,
- { nullptr, nullptr, PREC_NONE }, // FUNC,
+ { &GDScriptParser::parse_lambda, nullptr, PREC_NONE }, // FUNC,
{ nullptr, &GDScriptParser::parse_binary_operator, PREC_CONTENT_TEST }, // IN,
{ nullptr, &GDScriptParser::parse_binary_operator, PREC_TYPE_TEST }, // IS,
{ nullptr, nullptr, PREC_NONE }, // NAMESPACE,
@@ -3753,6 +3871,10 @@ void GDScriptParser::TreePrinter::print_dictionary(DictionaryNode *p_dictionary)
}
void GDScriptParser::TreePrinter::print_expression(ExpressionNode *p_expression) {
+ if (p_expression == nullptr) {
+ push_text("<invalid expression>");
+ return;
+ }
switch (p_expression->type) {
case Node::ARRAY:
print_array(static_cast<ArrayNode *>(p_expression));
@@ -3781,6 +3903,9 @@ void GDScriptParser::TreePrinter::print_expression(ExpressionNode *p_expression)
case Node::IDENTIFIER:
print_identifier(static_cast<IdentifierNode *>(p_expression));
break;
+ case Node::LAMBDA:
+ print_lambda(static_cast<LambdaNode *>(p_expression));
+ break;
case Node::LITERAL:
print_literal(static_cast<LiteralNode *>(p_expression));
break;
@@ -3840,12 +3965,17 @@ void GDScriptParser::TreePrinter::print_for(ForNode *p_for) {
decrease_indent();
}
-void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function) {
+void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function, const String &p_context) {
for (const List<AnnotationNode *>::Element *E = p_function->annotations.front(); E != nullptr; E = E->next()) {
print_annotation(E->get());
}
- push_text("Function ");
- print_identifier(p_function->identifier);
+ push_text(p_context);
+ push_text(" ");
+ if (p_function->identifier) {
+ print_identifier(p_function->identifier);
+ } else {
+ push_text("<anonymous>");
+ }
push_text("( ");
for (int i = 0; i < p_function->parameters.size(); i++) {
if (i > 0) {
@@ -3899,6 +4029,18 @@ 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 (int i = 0; i < p_lambda->captures.size(); i++) {
+ if (i > 0) {
+ push_text(" , ");
+ }
+ push_text(p_lambda->captures[i]->name.operator String());
+ }
+ push_line(" ]");
+}
+
void GDScriptParser::TreePrinter::print_literal(LiteralNode *p_literal) {
// Prefix for string types.
switch (p_literal->value.get_type()) {
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 272d21ffce..ee5e411cad 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -76,6 +76,7 @@ public:
struct GetNodeNode;
struct IdentifierNode;
struct IfNode;
+ struct LambdaNode;
struct LiteralNode;
struct MatchNode;
struct MatchBranchNode;
@@ -267,6 +268,7 @@ public:
GET_NODE,
IDENTIFIER,
IF,
+ LAMBDA,
LITERAL,
MATCH,
MATCH_BRANCH,
@@ -368,6 +370,7 @@ public:
Variant::Operator variant_op = Variant::OP_MAX;
ExpressionNode *assignee = nullptr;
ExpressionNode *assigned_value = nullptr;
+ bool use_conversion_assign = false;
AssignmentNode() {
type = ASSIGNMENT;
@@ -728,6 +731,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;
@@ -771,6 +775,7 @@ public:
VariableNode *variable_source;
IdentifierNode *bind_source;
};
+ FunctionNode *source_function = nullptr;
int usages = 0; // Useful for binds/iterator variable.
@@ -789,6 +794,21 @@ public:
}
};
+ struct LambdaNode : public ExpressionNode {
+ FunctionNode *function = nullptr;
+ FunctionNode *parent_function = nullptr;
+ Vector<IdentifierNode *> captures;
+ Map<StringName, int> captures_indices;
+
+ bool has_name() const {
+ return function && function->identifier;
+ }
+
+ LambdaNode() {
+ type = LAMBDA;
+ }
+ };
+
struct LiteralNode : public ExpressionNode {
Variant value;
@@ -942,6 +962,7 @@ public:
IdentifierNode *bind;
};
StringName name;
+ FunctionNode *source_function = nullptr;
int start_line = 0, end_line = 0;
int start_column = 0, end_column = 0;
@@ -951,10 +972,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;
@@ -963,10 +985,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;
@@ -975,10 +998,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;
@@ -987,10 +1011,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;
@@ -1015,9 +1040,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();
@@ -1095,6 +1120,7 @@ public:
MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
int assignments = 0;
int usages = 0;
+ bool use_conversion_assign = false;
#ifdef TOOLS_ENABLED
String doc_description;
#endif // TOOLS_ENABLED
@@ -1191,6 +1217,8 @@ private:
CompletionCall completion_call;
List<CompletionCall> completion_call_stack;
bool passed_cursor = false;
+ bool in_lambda = false;
+ bool lambda_ended = false; // Marker for when a lambda ends, to apply an end of statement if needed.
typedef bool (GDScriptParser::*AnnotationAction)(const AnnotationNode *p_annotation, Node *p_target);
struct AnnotationInfo {
@@ -1278,10 +1306,11 @@ private:
GDScriptTokenizer::Token advance();
bool match(GDScriptTokenizer::Token::Type p_token_type);
- bool check(GDScriptTokenizer::Token::Type p_token_type);
+ bool check(GDScriptTokenizer::Token::Type p_token_type) const;
bool consume(GDScriptTokenizer::Token::Type p_token_type, const String &p_error_message);
- bool is_at_end();
- bool is_statement_end();
+ bool is_at_end() const;
+ bool is_statement_end_token() const;
+ bool is_statement_end() const;
void end_statement(const String &p_context);
void synchronize();
void push_multiline(bool p_state);
@@ -1299,7 +1328,8 @@ private:
EnumNode *parse_enum();
ParameterNode *parse_parameter();
FunctionNode *parse_function();
- SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr);
+ void parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type);
+ SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr, bool p_for_lambda = false);
// Annotations
AnnotationNode *parse_annotation(uint32_t p_valid_targets);
bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, int p_optional_arguments = 0, bool p_is_vararg = false);
@@ -1354,6 +1384,7 @@ private:
ExpressionNode *parse_await(ExpressionNode *p_previous_operand, bool p_can_assign);
ExpressionNode *parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign);
ExpressionNode *parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign);
+ ExpressionNode *parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign);
ExpressionNode *parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign);
TypeNode *parse_type(bool p_allow_void = false);
#ifdef TOOLS_ENABLED
@@ -1415,10 +1446,11 @@ public:
void print_expression(ExpressionNode *p_expression);
void print_enum(EnumNode *p_enum);
void print_for(ForNode *p_for);
- void print_function(FunctionNode *p_function);
+ void print_function(FunctionNode *p_function, const String &p_context = "Function");
void print_get_node(GetNodeNode *p_get_node);
void print_if(IfNode *p_if, bool p_is_elif = false);
void print_identifier(IdentifierNode *p_identifier);
+ void print_lambda(LambdaNode *p_lambda);
void print_literal(LiteralNode *p_literal);
void print_match(MatchNode *p_match);
void print_match_branch(MatchBranchNode *p_match_branch);
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index e432dfc891..3f14156dfa 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -242,6 +242,16 @@ void GDScriptTokenizer::set_multiline_mode(bool p_state) {
multiline_mode = p_state;
}
+void GDScriptTokenizer::push_expression_indented_block() {
+ indent_stack_stack.push_back(indent_stack);
+}
+
+void GDScriptTokenizer::pop_expression_indented_block() {
+ ERR_FAIL_COND(indent_stack_stack.size() == 0);
+ indent_stack = indent_stack_stack.back()->get();
+ indent_stack_stack.pop_back();
+}
+
int GDScriptTokenizer::get_cursor_line() const {
return cursor_line;
}
@@ -633,6 +643,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
push_error(error);
}
previous_was_underscore = true;
+ } else {
+ previous_was_underscore = false;
}
_advance();
}
@@ -704,6 +716,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
push_error(error);
}
previous_was_underscore = true;
+ } else {
+ previous_was_underscore = false;
}
_advance();
}
diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h
index bea4b14019..84b82c07f0 100644
--- a/modules/gdscript/gdscript_tokenizer.h
+++ b/modules/gdscript/gdscript_tokenizer.h
@@ -217,6 +217,7 @@ private:
Token last_newline;
int pending_indents = 0;
List<int> indent_stack;
+ List<List<int>> indent_stack_stack; // For lambdas, which require manipulating the indentation point.
List<char32_t> paren_stack;
char32_t indent_char = '\0';
int position = 0;
@@ -263,6 +264,8 @@ public:
void set_multiline_mode(bool p_state);
bool is_past_cursor() const;
static String get_token_name(Token::Type p_token_type);
+ void push_expression_indented_block(); // For lambdas, or blocks inside expressions.
+ void pop_expression_indented_block(); // For lambdas, or blocks inside expressions.
GDScriptTokenizer();
};
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index 8bf6a8b08b..56b0f6db83 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -33,6 +33,7 @@
#include "core/core_string_names.h"
#include "core/os/os.h"
#include "gdscript.h"
+#include "gdscript_lambda_callable.h"
Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_instance, Variant *p_stack, String &r_error) const {
int address = p_address & ADDR_MASK;
@@ -151,6 +152,44 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const
return err_text;
}
+void (*type_init_function_table[])(Variant *) = {
+ nullptr, // NIL (shouldn't be called).
+ &VariantInitializer<bool>::init, // BOOL.
+ &VariantInitializer<int64_t>::init, // INT.
+ &VariantInitializer<double>::init, // FLOAT.
+ &VariantInitializer<String>::init, // STRING.
+ &VariantInitializer<Vector2>::init, // VECTOR2.
+ &VariantInitializer<Vector2i>::init, // VECTOR2I.
+ &VariantInitializer<Rect2>::init, // RECT2.
+ &VariantInitializer<Rect2i>::init, // RECT2I.
+ &VariantInitializer<Vector3>::init, // VECTOR3.
+ &VariantInitializer<Vector3i>::init, // VECTOR3I.
+ &VariantInitializer<Transform2D>::init, // TRANSFORM2D.
+ &VariantInitializer<Plane>::init, // PLANE.
+ &VariantInitializer<Quat>::init, // QUAT.
+ &VariantInitializer<AABB>::init, // AABB.
+ &VariantInitializer<Basis>::init, // BASIS.
+ &VariantInitializer<Transform>::init, // TRANSFORM.
+ &VariantInitializer<Color>::init, // COLOR.
+ &VariantInitializer<StringName>::init, // STRING_NAME.
+ &VariantInitializer<NodePath>::init, // NODE_PATH.
+ &VariantInitializer<RID>::init, // RID.
+ &VariantTypeAdjust<Object *>::adjust, // OBJECT.
+ &VariantInitializer<Callable>::init, // CALLABLE.
+ &VariantInitializer<Signal>::init, // SIGNAL.
+ &VariantInitializer<Dictionary>::init, // DICTIONARY.
+ &VariantInitializer<Array>::init, // ARRAY.
+ &VariantInitializer<PackedByteArray>::init, // PACKED_BYTE_ARRAY.
+ &VariantInitializer<PackedInt32Array>::init, // PACKED_INT32_ARRAY.
+ &VariantInitializer<PackedInt64Array>::init, // PACKED_INT64_ARRAY.
+ &VariantInitializer<PackedFloat32Array>::init, // PACKED_FLOAT32_ARRAY.
+ &VariantInitializer<PackedFloat64Array>::init, // PACKED_FLOAT64_ARRAY.
+ &VariantInitializer<PackedStringArray>::init, // PACKED_STRING_ARRAY.
+ &VariantInitializer<PackedVector2Array>::init, // PACKED_VECTOR2_ARRAY.
+ &VariantInitializer<PackedVector3Array>::init, // PACKED_VECTOR3_ARRAY.
+ &VariantInitializer<PackedColorArray>::init, // PACKED_COLOR_ARRAY.
+};
+
#if defined(__GNUC__)
#define OPCODES_TABLE \
static const void *switch_table_ops[] = { \
@@ -195,6 +234,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const
&&OPCODE_CALL_SELF_BASE, \
&&OPCODE_CALL_METHOD_BIND, \
&&OPCODE_CALL_METHOD_BIND_RET, \
+ &&OPCODE_CALL_BUILTIN_STATIC, \
&&OPCODE_CALL_PTRCALL_NO_RETURN, \
&&OPCODE_CALL_PTRCALL_BOOL, \
&&OPCODE_CALL_PTRCALL_INT, \
@@ -232,6 +272,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const
&&OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY, \
&&OPCODE_AWAIT, \
&&OPCODE_AWAIT_RESUME, \
+ &&OPCODE_CREATE_LAMBDA, \
&&OPCODE_JUMP, \
&&OPCODE_JUMP_IF, \
&&OPCODE_JUMP_IF_NOT, \
@@ -282,6 +323,40 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const
&&OPCODE_ITERATE_PACKED_COLOR_ARRAY, \
&&OPCODE_ITERATE_OBJECT, \
&&OPCODE_STORE_NAMED_GLOBAL, \
+ &&OPCODE_TYPE_ADJUST_BOOL, \
+ &&OPCODE_TYPE_ADJUST_INT, \
+ &&OPCODE_TYPE_ADJUST_FLOAT, \
+ &&OPCODE_TYPE_ADJUST_STRING, \
+ &&OPCODE_TYPE_ADJUST_VECTOR2, \
+ &&OPCODE_TYPE_ADJUST_VECTOR2I, \
+ &&OPCODE_TYPE_ADJUST_RECT2, \
+ &&OPCODE_TYPE_ADJUST_RECT2I, \
+ &&OPCODE_TYPE_ADJUST_VECTOR3, \
+ &&OPCODE_TYPE_ADJUST_VECTOR3I, \
+ &&OPCODE_TYPE_ADJUST_TRANSFORM2D, \
+ &&OPCODE_TYPE_ADJUST_PLANE, \
+ &&OPCODE_TYPE_ADJUST_QUAT, \
+ &&OPCODE_TYPE_ADJUST_AABB, \
+ &&OPCODE_TYPE_ADJUST_BASIS, \
+ &&OPCODE_TYPE_ADJUST_TRANSFORM, \
+ &&OPCODE_TYPE_ADJUST_COLOR, \
+ &&OPCODE_TYPE_ADJUST_STRING_NAME, \
+ &&OPCODE_TYPE_ADJUST_NODE_PATH, \
+ &&OPCODE_TYPE_ADJUST_RID, \
+ &&OPCODE_TYPE_ADJUST_OBJECT, \
+ &&OPCODE_TYPE_ADJUST_CALLABLE, \
+ &&OPCODE_TYPE_ADJUST_SIGNAL, \
+ &&OPCODE_TYPE_ADJUST_DICTIONARY, \
+ &&OPCODE_TYPE_ADJUST_ARRAY, \
+ &&OPCODE_TYPE_ADJUST_PACKED_BYTE_ARRAY, \
+ &&OPCODE_TYPE_ADJUST_PACKED_INT32_ARRAY, \
+ &&OPCODE_TYPE_ADJUST_PACKED_INT64_ARRAY, \
+ &&OPCODE_TYPE_ADJUST_PACKED_FLOAT32_ARRAY, \
+ &&OPCODE_TYPE_ADJUST_PACKED_FLOAT64_ARRAY, \
+ &&OPCODE_TYPE_ADJUST_PACKED_STRING_ARRAY, \
+ &&OPCODE_TYPE_ADJUST_PACKED_VECTOR2_ARRAY, \
+ &&OPCODE_TYPE_ADJUST_PACKED_VECTOR3_ARRAY, \
+ &&OPCODE_TYPE_ADJUST_PACKED_COLOR_ARRAY, \
&&OPCODE_ASSERT, \
&&OPCODE_BREAKPOINT, \
&&OPCODE_LINE, \
@@ -455,6 +530,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
memnew_placement(&stack[ADDR_STACK_CLASS], Variant(script));
+ for (const Map<int, Variant::Type>::Element *E = temporary_slots.front(); E; E = E->next()) {
+ type_init_function_table[E->get()](&stack[E->key()]);
+ }
+
String err_text;
#ifdef DEBUG_ENABLED
@@ -1418,13 +1497,17 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (err.error != Callable::CallError::CALL_OK) {
String methodstr = *methodname;
String basestr = _get_var_type(base);
+ bool is_callable = false;
if (methodstr == "call") {
- if (argc >= 1) {
+ if (argc >= 1 && base->get_type() != Variant::CALLABLE) {
methodstr = String(*argptrs[0]) + " (via call)";
if (err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) {
err.argument += 1;
}
+ } else {
+ methodstr = base->operator String() + " (Callable)";
+ is_callable = true;
}
} else if (methodstr == "free") {
if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) {
@@ -1444,7 +1527,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
}
}
- err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs);
+ err_text = _get_call_error(err, "function '" + methodstr + (is_callable ? "" : "' in base '" + basestr) + "'", (const Variant **)argptrs);
OPCODE_BREAK;
}
#endif
@@ -1533,6 +1616,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
+ OPCODE(OPCODE_CALL_BUILTIN_STATIC) {
+ CHECK_SPACE(4 + instr_arg_count);
+
+ ip += instr_arg_count;
+
+ GD_ERR_BREAK(_code_ptr[ip + 1] < 0 || _code_ptr[ip + 1] >= Variant::VARIANT_MAX);
+ Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 1];
+
+ int methodname_idx = _code_ptr[ip + 2];
+ GD_ERR_BREAK(methodname_idx < 0 || methodname_idx >= _global_names_count);
+ const StringName *methodname = &_global_names_ptr[methodname_idx];
+
+ int argc = _code_ptr[ip + 3];
+ GD_ERR_BREAK(argc < 0);
+
+ GET_INSTRUCTION_ARG(ret, argc);
+
+ const Variant **argptrs = const_cast<const Variant **>(instruction_args);
+
+#ifdef DEBUG_ENABLED
+ uint64_t call_time = 0;
+
+ if (GDScriptLanguage::get_singleton()->profiling) {
+ call_time = OS::get_singleton()->get_ticks_usec();
+ }
+#endif
+
+ Callable::CallError err;
+ Variant::call_static(builtin_type, *methodname, argptrs, argc, *ret, err);
+
+#ifdef DEBUG_ENABLED
+ if (GDScriptLanguage::get_singleton()->profiling) {
+ function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
+ }
+
+ if (err.error != Callable::CallError::CALL_OK) {
+ err_text = _get_call_error(err, "static function '" + methodname->operator String() + "' in type '" + Variant::get_type_name(builtin_type) + "'", argptrs);
+ OPCODE_BREAK;
+ }
+#endif
+
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
#ifdef DEBUG_ENABLED
#define OPCODE_CALL_PTR(m_type) \
OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \
@@ -1842,7 +1970,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#ifdef DEBUG_ENABLED
if (err.error != Callable::CallError::CALL_OK) {
// TODO: Add this information in debug.
- String methodstr = "<unkown function>";
+ String methodstr = "<unknown function>";
if (dst->get_type() == Variant::STRING) {
// Call provided error string.
err_text = "Error calling GDScript utility function '" + methodstr + "': " + String(*dst);
@@ -2023,6 +2151,34 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
+ OPCODE(OPCODE_CREATE_LAMBDA) {
+ CHECK_SPACE(2 + instr_arg_count);
+
+ 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;
+ }
+
+ GDScriptLambdaCallable *callable = memnew(GDScriptLambdaCallable(Ref<GDScript>(script), lambda, captures));
+
+ GET_INSTRUCTION_ARG(result, captures_count);
+ *result = Callable(callable);
+
+ ip += 3;
+ }
+ DISPATCH_OPCODE;
+
OPCODE(OPCODE_JUMP) {
CHECK_SPACE(2);
int to = _code_ptr[ip + 1];
@@ -2973,6 +3129,50 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
+#define OPCODE_TYPE_ADJUST(m_v_type, m_c_type) \
+ OPCODE(OPCODE_TYPE_ADJUST_##m_v_type) { \
+ CHECK_SPACE(2); \
+ GET_INSTRUCTION_ARG(arg, 0); \
+ VariantTypeAdjust<m_c_type>::adjust(arg); \
+ ip += 2; \
+ } \
+ DISPATCH_OPCODE
+
+ OPCODE_TYPE_ADJUST(BOOL, bool);
+ OPCODE_TYPE_ADJUST(INT, int64_t);
+ OPCODE_TYPE_ADJUST(FLOAT, double);
+ OPCODE_TYPE_ADJUST(STRING, String);
+ OPCODE_TYPE_ADJUST(VECTOR2, Vector2);
+ OPCODE_TYPE_ADJUST(VECTOR2I, Vector2i);
+ OPCODE_TYPE_ADJUST(RECT2, Rect2);
+ OPCODE_TYPE_ADJUST(RECT2I, Rect2i);
+ OPCODE_TYPE_ADJUST(VECTOR3, Vector3);
+ OPCODE_TYPE_ADJUST(VECTOR3I, Vector3i);
+ OPCODE_TYPE_ADJUST(TRANSFORM2D, Transform2D);
+ OPCODE_TYPE_ADJUST(PLANE, Plane);
+ OPCODE_TYPE_ADJUST(QUAT, Quat);
+ OPCODE_TYPE_ADJUST(AABB, AABB);
+ OPCODE_TYPE_ADJUST(BASIS, Basis);
+ OPCODE_TYPE_ADJUST(TRANSFORM, Transform);
+ OPCODE_TYPE_ADJUST(COLOR, Color);
+ OPCODE_TYPE_ADJUST(STRING_NAME, StringName);
+ OPCODE_TYPE_ADJUST(NODE_PATH, NodePath);
+ OPCODE_TYPE_ADJUST(RID, RID);
+ OPCODE_TYPE_ADJUST(OBJECT, Object *);
+ OPCODE_TYPE_ADJUST(CALLABLE, Callable);
+ OPCODE_TYPE_ADJUST(SIGNAL, Signal);
+ OPCODE_TYPE_ADJUST(DICTIONARY, Dictionary);
+ OPCODE_TYPE_ADJUST(ARRAY, Array);
+ OPCODE_TYPE_ADJUST(PACKED_BYTE_ARRAY, PackedByteArray);
+ OPCODE_TYPE_ADJUST(PACKED_INT32_ARRAY, PackedInt32Array);
+ OPCODE_TYPE_ADJUST(PACKED_INT64_ARRAY, PackedInt64Array);
+ OPCODE_TYPE_ADJUST(PACKED_FLOAT32_ARRAY, PackedFloat32Array);
+ OPCODE_TYPE_ADJUST(PACKED_FLOAT64_ARRAY, PackedFloat64Array);
+ OPCODE_TYPE_ADJUST(PACKED_STRING_ARRAY, PackedStringArray);
+ OPCODE_TYPE_ADJUST(PACKED_VECTOR2_ARRAY, PackedVector2Array);
+ OPCODE_TYPE_ADJUST(PACKED_VECTOR3_ARRAY, PackedVector3Array);
+ OPCODE_TYPE_ADJUST(PACKED_COLOR_ARRAY, PackedColorArray);
+
OPCODE(OPCODE_ASSERT) {
CHECK_SPACE(3);
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp
index e63b6ab20e..15236d900d 100644
--- a/modules/gdscript/language_server/gdscript_extend_parser.cpp
+++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp
@@ -49,8 +49,9 @@ void ExtendGDScriptParser::update_diagnostics() {
diagnostic.code = -1;
lsp::Range range;
lsp::Position pos;
- int line = LINE_NUMBER_TO_INDEX(error.line);
- const String &line_text = get_lines()[line];
+ const PackedStringArray lines = get_lines();
+ int line = CLAMP(LINE_NUMBER_TO_INDEX(error.line), 0, lines.size() - 1);
+ const String &line_text = lines[line];
pos.line = line;
pos.character = line_text.length() - line_text.strip_edges(true, false).length();
range.start = pos;
@@ -361,24 +362,73 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN
r_symbol.detail += " -> " + p_func->get_datatype().to_string();
}
- for (int i = 0; i < p_func->body->locals.size(); i++) {
- const SuiteNode::Local &local = p_func->body->locals[i];
- lsp::DocumentSymbol symbol;
- symbol.name = local.name;
- symbol.kind = local.type == SuiteNode::Local::CONSTANT ? lsp::SymbolKind::Constant : lsp::SymbolKind::Variable;
- symbol.range.start.line = LINE_NUMBER_TO_INDEX(local.start_line);
- symbol.range.start.character = LINE_NUMBER_TO_INDEX(local.start_column);
- symbol.range.end.line = LINE_NUMBER_TO_INDEX(local.end_line);
- symbol.range.end.character = LINE_NUMBER_TO_INDEX(local.end_column);
- symbol.uri = uri;
- symbol.script_path = path;
- symbol.detail = SuiteNode::Local::CONSTANT ? "const " : "var ";
- symbol.detail += symbol.name;
- if (local.get_datatype().is_hard_type()) {
- symbol.detail += ": " + local.get_datatype().to_string();
+ List<GDScriptParser::SuiteNode *> function_nodes;
+
+ List<GDScriptParser::Node *> node_stack;
+ node_stack.push_back(p_func->body);
+
+ while (!node_stack.is_empty()) {
+ GDScriptParser::Node *node = node_stack[0];
+ node_stack.pop_front();
+
+ switch (node->type) {
+ case GDScriptParser::TypeNode::IF: {
+ GDScriptParser::IfNode *if_node = (GDScriptParser::IfNode *)node;
+ node_stack.push_back(if_node->true_block);
+ if (if_node->false_block) {
+ node_stack.push_back(if_node->false_block);
+ }
+ } break;
+
+ case GDScriptParser::TypeNode::FOR: {
+ GDScriptParser::ForNode *for_node = (GDScriptParser::ForNode *)node;
+ node_stack.push_back(for_node->loop);
+ } break;
+
+ case GDScriptParser::TypeNode::WHILE: {
+ GDScriptParser::WhileNode *while_node = (GDScriptParser::WhileNode *)node;
+ node_stack.push_back(while_node->loop);
+ } break;
+
+ case GDScriptParser::TypeNode::MATCH_BRANCH: {
+ GDScriptParser::MatchBranchNode *match_node = (GDScriptParser::MatchBranchNode *)node;
+ node_stack.push_back(match_node->block);
+ } break;
+
+ case GDScriptParser::TypeNode::SUITE: {
+ GDScriptParser::SuiteNode *suite_node = (GDScriptParser::SuiteNode *)node;
+ function_nodes.push_back(suite_node);
+ for (int i = 0; i < suite_node->statements.size(); ++i) {
+ node_stack.push_back(suite_node->statements[i]);
+ }
+ } break;
+
+ default:
+ continue;
+ }
+ }
+
+ for (List<GDScriptParser::SuiteNode *>::Element *N = function_nodes.front(); N; N = N->next()) {
+ const GDScriptParser::SuiteNode *suite_node = N->get();
+ for (int i = 0; i < suite_node->locals.size(); i++) {
+ const SuiteNode::Local &local = suite_node->locals[i];
+ lsp::DocumentSymbol symbol;
+ symbol.name = local.name;
+ symbol.kind = local.type == SuiteNode::Local::CONSTANT ? lsp::SymbolKind::Constant : lsp::SymbolKind::Variable;
+ symbol.range.start.line = LINE_NUMBER_TO_INDEX(local.start_line);
+ symbol.range.start.character = LINE_NUMBER_TO_INDEX(local.start_column);
+ symbol.range.end.line = LINE_NUMBER_TO_INDEX(local.end_line);
+ symbol.range.end.character = LINE_NUMBER_TO_INDEX(local.end_column);
+ symbol.uri = uri;
+ symbol.script_path = path;
+ symbol.detail = local.type == SuiteNode::Local::CONSTANT ? "const " : "var ";
+ symbol.detail += symbol.name;
+ if (local.get_datatype().is_hard_type()) {
+ symbol.detail += ": " + local.get_datatype().to_string();
+ }
+ symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(local.start_line));
+ r_symbol.children.push_back(symbol);
}
- symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(local.start_line));
- r_symbol.children.push_back(symbol);
}
}
diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp
index 912c9a174e..c16a7fa889 100644
--- a/modules/gdscript/language_server/gdscript_language_protocol.cpp
+++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp
@@ -32,7 +32,6 @@
#include "core/config/project_settings.h"
#include "core/io/json.h"
-#include "core/os/copymem.h"
#include "editor/doc_tools.h"
#include "editor/editor_log.h"
#include "editor/editor_node.h"
@@ -256,7 +255,7 @@ void GDScriptLanguageProtocol::poll() {
}
}
-Error GDScriptLanguageProtocol::start(int p_port, const IP_Address &p_bind_ip) {
+Error GDScriptLanguageProtocol::start(int p_port, const IPAddress &p_bind_ip) {
return server->listen(p_port, p_bind_ip);
}
diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h
index 8b08ae0655..a5c5a233b1 100644
--- a/modules/gdscript/language_server/gdscript_language_protocol.h
+++ b/modules/gdscript/language_server/gdscript_language_protocol.h
@@ -69,7 +69,7 @@ private:
static GDScriptLanguageProtocol *singleton;
HashMap<int, Ref<LSPeer>> clients;
- Ref<TCP_Server> server;
+ Ref<TCPServer> server;
int latest_client_id = 0;
int next_client_id = 0;
@@ -97,7 +97,7 @@ public:
_FORCE_INLINE_ bool is_initialized() const { return _initialized; }
void poll();
- Error start(int p_port, const IP_Address &p_bind_ip);
+ Error start(int p_port, const IPAddress &p_bind_ip);
void stop();
void notify_client(const String &p_method, const Variant &p_params = Variant(), int p_client_id = -1);
diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp
index 98ada9de4d..340a7b9343 100644
--- a/modules/gdscript/language_server/gdscript_language_server.cpp
+++ b/modules/gdscript/language_server/gdscript_language_server.cpp
@@ -78,7 +78,7 @@ void GDScriptLanguageServer::thread_main(void *p_userdata) {
void GDScriptLanguageServer::start() {
port = (int)_EDITOR_GET("network/language_server/remote_port");
use_thread = (bool)_EDITOR_GET("network/language_server/use_thread");
- if (protocol.start(port, IP_Address("127.0.0.1")) == OK) {
+ if (protocol.start(port, IPAddress("127.0.0.1")) == OK) {
EditorNode::get_log()->add_message("--- GDScript language server started ---", EditorLog::MSG_TYPE_EDITOR);
if (use_thread) {
thread_running = true;
diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp
index 9f2373bf56..030633274c 100644
--- a/modules/gdscript/language_server/gdscript_text_document.cpp
+++ b/modules/gdscript/language_server/gdscript_text_document.cpp
@@ -400,6 +400,7 @@ GDScriptTextDocument::~GDScriptTextDocument() {
void GDScriptTextDocument::sync_script_content(const String &p_path, const String &p_content) {
String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_path);
GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content);
+ EditorFileSystem::get_singleton()->update_file(path);
}
void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) {
diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp
index 69cad1a335..9b7b2b36b4 100644
--- a/modules/gdscript/language_server/gdscript_workspace.cpp
+++ b/modules/gdscript/language_server/gdscript_workspace.cpp
@@ -42,6 +42,7 @@
#include "scene/resources/packed_scene.h"
void GDScriptWorkspace::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("didDeleteFiles"), &GDScriptWorkspace::did_delete_files);
ClassDB::bind_method(D_METHOD("symbol"), &GDScriptWorkspace::symbol);
ClassDB::bind_method(D_METHOD("parse_script", "path", "content"), &GDScriptWorkspace::parse_script);
ClassDB::bind_method(D_METHOD("parse_local_script", "path"), &GDScriptWorkspace::parse_local_script);
@@ -51,6 +52,16 @@ void GDScriptWorkspace::_bind_methods() {
ClassDB::bind_method(D_METHOD("generate_script_api", "path"), &GDScriptWorkspace::generate_script_api);
}
+void GDScriptWorkspace::did_delete_files(const Dictionary &p_params) {
+ Array files = p_params["files"];
+ for (int i = 0; i < files.size(); ++i) {
+ Dictionary file = files[i];
+ String uri = file["uri"];
+ String path = get_file_path(uri);
+ parse_script(path, "");
+ }
+}
+
void GDScriptWorkspace::remove_cache_parser(const String &p_path) {
Map<String, ExtendGDScriptParser *>::Element *parser = parse_results.find(p_path);
Map<String, ExtendGDScriptParser *>::Element *script = scripts.find(p_path);
diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h
index 7fd8bfcf20..27616a2989 100644
--- a/modules/gdscript/language_server/gdscript_workspace.h
+++ b/modules/gdscript/language_server/gdscript_workspace.h
@@ -89,6 +89,7 @@ public:
void resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list);
Dictionary generate_script_api(const String &p_path);
Error resolve_signature(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::SignatureHelp &r_signature);
+ void did_delete_files(const Dictionary &p_params);
GDScriptWorkspace();
~GDScriptWorkspace();
diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp
index 6635098be2..47bcfeaefc 100644
--- a/modules/gdscript/language_server/lsp.hpp
+++ b/modules/gdscript/language_server/lsp.hpp
@@ -1528,6 +1528,114 @@ struct SignatureHelp {
}
};
+/**
+ * A pattern to describe in which file operation requests or notifications
+ * the server is interested in.
+ */
+struct FileOperationPattern {
+ /**
+ * The glob pattern to match.
+ */
+ String glob = "**/*.gd";
+
+ /**
+ * Whether to match `file`s or `folder`s with this pattern.
+ *
+ * Matches both if undefined.
+ */
+ String matches = "file";
+
+ Dictionary to_json() const {
+ Dictionary dict;
+
+ dict["glob"] = glob;
+ dict["matches"] = matches;
+
+ return dict;
+ }
+};
+
+/**
+ * A filter to describe in which file operation requests or notifications
+ * the server is interested in.
+ */
+struct FileOperationFilter {
+ /**
+ * The actual file operation pattern.
+ */
+ FileOperationPattern pattern;
+
+ Dictionary to_json() const {
+ Dictionary dict;
+
+ dict["pattern"] = pattern.to_json();
+
+ return dict;
+ }
+};
+
+/**
+ * The options to register for file operations.
+ */
+struct FileOperationRegistrationOptions {
+ /**
+ * The actual filters.
+ */
+ Vector<FileOperationFilter> filters;
+
+ FileOperationRegistrationOptions() {
+ filters.push_back(FileOperationFilter());
+ }
+
+ Dictionary to_json() const {
+ Dictionary dict;
+
+ Array filts;
+ for (int i = 0; i < filters.size(); i++) {
+ filts.push_back(filters[i].to_json());
+ }
+ dict["filters"] = filts;
+
+ return dict;
+ }
+};
+
+/**
+ * The server is interested in file notifications/requests.
+ */
+struct FileOperations {
+ /**
+ * The server is interested in receiving didDeleteFiles file notifications.
+ */
+ FileOperationRegistrationOptions didDelete;
+
+ Dictionary to_json() const {
+ Dictionary dict;
+
+ dict["didDelete"] = didDelete.to_json();
+
+ return dict;
+ }
+};
+
+/**
+ * Workspace specific server capabilities
+ */
+struct Workspace {
+ /**
+ * The server is interested in file notifications/requests.
+ */
+ FileOperations fileOperations;
+
+ Dictionary to_json() const {
+ Dictionary dict;
+
+ dict["fileOperations"] = fileOperations.to_json();
+
+ return dict;
+ }
+};
+
struct ServerCapabilities {
/**
* Defines how text documents are synced. Is either a detailed structure defining each notification or
@@ -1590,6 +1698,11 @@ struct ServerCapabilities {
bool workspaceSymbolProvider = true;
/**
+ * The server supports workspace folder.
+ */
+ Workspace workspace;
+
+ /**
* The server provides code actions. The `CodeActionOptions` return type is only
* valid if the client signals code action literal support via the property
* `textDocument.codeAction.codeActionLiteralSupport`.
@@ -1676,6 +1789,7 @@ struct ServerCapabilities {
dict["documentHighlightProvider"] = documentHighlightProvider;
dict["documentSymbolProvider"] = documentSymbolProvider;
dict["workspaceSymbolProvider"] = workspaceSymbolProvider;
+ dict["workspace"] = workspace.to_json();
dict["codeActionProvider"] = codeActionProvider;
dict["documentFormattingProvider"] = documentFormattingProvider;
dict["documentRangeFormattingProvider"] = documentRangeFormattingProvider;
diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp
index 19fd3daf20..2d2f94f5e0 100644
--- a/modules/gdscript/register_types.cpp
+++ b/modules/gdscript/register_types.cpp
@@ -163,19 +163,19 @@ void unregister_gdscript_types() {
#ifdef TESTS_ENABLED
void test_tokenizer() {
- TestGDScript::test(TestGDScript::TestType::TEST_TOKENIZER);
+ GDScriptTests::test(GDScriptTests::TestType::TEST_TOKENIZER);
}
void test_parser() {
- TestGDScript::test(TestGDScript::TestType::TEST_PARSER);
+ GDScriptTests::test(GDScriptTests::TestType::TEST_PARSER);
}
void test_compiler() {
- TestGDScript::test(TestGDScript::TestType::TEST_COMPILER);
+ GDScriptTests::test(GDScriptTests::TestType::TEST_COMPILER);
}
void test_bytecode() {
- TestGDScript::test(TestGDScript::TestType::TEST_BYTECODE);
+ GDScriptTests::test(GDScriptTests::TestType::TEST_BYTECODE);
}
REGISTER_TEST_COMMAND("gdscript-tokenizer", &test_tokenizer);
diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp
new file mode 100644
index 0000000000..e20e427597
--- /dev/null
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -0,0 +1,583 @@
+/*************************************************************************/
+/* gdscript_test_runner.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "gdscript_test_runner.h"
+
+#include "../gdscript.h"
+#include "../gdscript_analyzer.h"
+#include "../gdscript_compiler.h"
+#include "../gdscript_parser.h"
+
+#include "core/config/project_settings.h"
+#include "core/core_string_names.h"
+#include "core/io/file_access_pack.h"
+#include "core/os/dir_access.h"
+#include "core/os/os.h"
+#include "core/string/string_builder.h"
+#include "scene/resources/packed_scene.h"
+
+#include "tests/test_macros.h"
+
+namespace GDScriptTests {
+
+void init_autoloads() {
+ Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
+
+ // First pass, add the constants so they exist before any script is loaded.
+ for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
+ const ProjectSettings::AutoloadInfo &info = E->get();
+
+ if (info.is_singleton) {
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ ScriptServer::get_language(i)->add_global_constant(info.name, Variant());
+ }
+ }
+ }
+
+ // Second pass, load into global constants.
+ for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
+ const ProjectSettings::AutoloadInfo &info = E->get();
+
+ if (!info.is_singleton) {
+ // Skip non-singletons since we don't have a scene tree here anyway.
+ continue;
+ }
+
+ RES res = ResourceLoader::load(info.path);
+ ERR_CONTINUE_MSG(res.is_null(), "Can't autoload: " + info.path);
+ Node *n = nullptr;
+ if (res->is_class("PackedScene")) {
+ Ref<PackedScene> ps = res;
+ n = ps->instance();
+ } else if (res->is_class("Script")) {
+ Ref<Script> script_res = res;
+ StringName ibt = script_res->get_instance_base_type();
+ bool valid_type = ClassDB::is_parent_class(ibt, "Node");
+ ERR_CONTINUE_MSG(!valid_type, "Script does not inherit a Node: " + info.path);
+
+ Object *obj = ClassDB::instance(ibt);
+
+ ERR_CONTINUE_MSG(obj == nullptr,
+ "Cannot instance script for autoload, expected 'Node' inheritance, got: " +
+ String(ibt));
+
+ n = Object::cast_to<Node>(obj);
+ n->set_script(script_res);
+ }
+
+ ERR_CONTINUE_MSG(!n, "Path in autoload not a node or script: " + info.path);
+ n->set_name(info.name);
+
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ ScriptServer::get_language(i)->add_global_constant(info.name, n);
+ }
+ }
+}
+
+void init_language(const String &p_base_path) {
+ // Setup project settings since it's needed by the languages to get the global scripts.
+ // This also sets up the base resource path.
+ Error err = ProjectSettings::get_singleton()->setup(p_base_path, String(), true);
+ if (err) {
+ print_line("Could not load project settings.");
+ // Keep going since some scripts still work without this.
+ }
+
+ // Initialize the language for the test routine.
+ GDScriptLanguage::get_singleton()->init();
+ init_autoloads();
+}
+
+void finish_language() {
+ GDScriptLanguage::get_singleton()->finish();
+ ScriptServer::global_classes_clear();
+}
+
+StringName GDScriptTestRunner::test_function_name;
+
+GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_language) {
+ test_function_name = StaticCString::create("test");
+ do_init_languages = p_init_language;
+
+ source_dir = p_source_dir;
+ if (!source_dir.ends_with("/")) {
+ source_dir += "/";
+ }
+
+ if (do_init_languages) {
+ init_language(p_source_dir);
+
+ // Enable all warnings for GDScript, so we can test them.
+ ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true);
+ for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
+ String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower();
+ ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/" + warning, true);
+ }
+ }
+
+ // Enable printing to show results
+ _print_line_enabled = true;
+ _print_error_enabled = true;
+}
+
+GDScriptTestRunner::~GDScriptTestRunner() {
+ test_function_name = StringName();
+ if (do_init_languages) {
+ finish_language();
+ }
+}
+
+int GDScriptTestRunner::run_tests() {
+ if (!make_tests()) {
+ FAIL("An error occurred while making the tests.");
+ return -1;
+ }
+
+ if (!generate_class_index()) {
+ FAIL("An error occurred while generating class index.");
+ return -1;
+ }
+
+ int failed = 0;
+ for (int i = 0; i < tests.size(); i++) {
+ GDScriptTest test = tests[i];
+ GDScriptTest::TestResult result = test.run_test();
+
+ String expected = FileAccess::get_file_as_string(test.get_output_file());
+ INFO(test.get_source_file());
+ if (!result.passed) {
+ INFO(expected);
+ failed++;
+ }
+
+ CHECK_MESSAGE(result.passed, (result.passed ? String() : result.output));
+ }
+
+ return failed;
+}
+
+bool GDScriptTestRunner::generate_outputs() {
+ is_generating = true;
+
+ if (!make_tests()) {
+ print_line("Failed to generate a test output.");
+ return false;
+ }
+
+ if (!generate_class_index()) {
+ return false;
+ }
+
+ for (int i = 0; i < tests.size(); i++) {
+ OS::get_singleton()->print(".");
+ GDScriptTest test = tests[i];
+ bool result = test.generate_output();
+
+ if (!result) {
+ print_line("\nCould not generate output for " + test.get_source_file());
+ return false;
+ }
+ }
+ print_line("\nGenerated output files for " + itos(tests.size()) + " tests successfully.");
+
+ return true;
+}
+
+bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) {
+ Error err = OK;
+ DirAccessRef dir(DirAccess::open(p_dir, &err));
+
+ if (err != OK) {
+ return false;
+ }
+
+ String current_dir = dir->get_current_dir();
+
+ dir->list_dir_begin();
+ String next = dir->get_next();
+
+ while (!next.is_empty()) {
+ if (dir->current_is_dir()) {
+ if (next == "." || next == "..") {
+ next = dir->get_next();
+ continue;
+ }
+ if (!make_tests_for_dir(current_dir.plus_file(next))) {
+ return false;
+ }
+ } else {
+ if (next.get_extension().to_lower() == "gd") {
+ String out_file = next.get_basename() + ".out";
+ if (!is_generating && !dir->file_exists(out_file)) {
+ ERR_FAIL_V_MSG(false, "Could not find output file for " + next);
+ }
+ GDScriptTest test(current_dir.plus_file(next), current_dir.plus_file(out_file), source_dir);
+ tests.push_back(test);
+ }
+ }
+
+ next = dir->get_next();
+ }
+
+ dir->list_dir_end();
+
+ return true;
+}
+
+bool GDScriptTestRunner::make_tests() {
+ Error err = OK;
+ DirAccessRef dir(DirAccess::open(source_dir, &err));
+
+ ERR_FAIL_COND_V_MSG(err != OK, false, "Could not open specified test directory.");
+
+ source_dir = dir->get_current_dir() + "/"; // Make it absolute path.
+ return make_tests_for_dir(dir->get_current_dir());
+}
+
+bool GDScriptTestRunner::generate_class_index() {
+ StringName gdscript_name = GDScriptLanguage::get_singleton()->get_name();
+ for (int i = 0; i < tests.size(); i++) {
+ GDScriptTest test = tests[i];
+ String base_type;
+
+ String class_name = GDScriptLanguage::get_singleton()->get_global_class_name(test.get_source_file(), &base_type);
+ if (class_name == String()) {
+ continue;
+ }
+ ERR_FAIL_COND_V_MSG(ScriptServer::is_global_class(class_name), false,
+ "Class name '" + class_name + "' from " + test.get_source_file() + " is already used in " + ScriptServer::get_global_class_path(class_name));
+
+ ScriptServer::add_global_class(class_name, base_type, gdscript_name, test.get_source_file());
+ }
+ return true;
+}
+
+GDScriptTest::GDScriptTest(const String &p_source_path, const String &p_output_path, const String &p_base_dir) {
+ source_file = p_source_path;
+ output_file = p_output_path;
+ base_dir = p_base_dir;
+ _print_handler.printfunc = print_handler;
+ _error_handler.errfunc = error_handler;
+}
+
+void GDScriptTestRunner::handle_cmdline() {
+ List<String> cmdline_args = OS::get_singleton()->get_cmdline_args();
+ // TODO: this could likely be ported to use test commands:
+ // https://github.com/godotengine/godot/pull/41355
+ // Currently requires to startup the whole engine, which is slow.
+ String test_cmd = "--gdscript-test";
+ String gen_cmd = "--gdscript-generate-tests";
+
+ for (List<String>::Element *E = cmdline_args.front(); E != nullptr; E = E->next()) {
+ String &cmd = E->get();
+ if (cmd == test_cmd || cmd == gen_cmd) {
+ if (E->next() == nullptr) {
+ ERR_PRINT("Needed a path for the test files.");
+ exit(-1);
+ }
+
+ const String &path = E->next()->get();
+
+ GDScriptTestRunner runner(path, false);
+ int failed = 0;
+ if (cmd == test_cmd) {
+ failed = runner.run_tests();
+ } else {
+ bool completed = runner.generate_outputs();
+ failed = completed ? 0 : -1;
+ }
+ exit(failed);
+ }
+ }
+}
+
+void GDScriptTest::enable_stdout() {
+ // TODO: this could likely be handled by doctest or `tests/test_macros.h`.
+ OS::get_singleton()->set_stdout_enabled(true);
+ OS::get_singleton()->set_stderr_enabled(true);
+}
+
+void GDScriptTest::disable_stdout() {
+ // TODO: this could likely be handled by doctest or `tests/test_macros.h`.
+ OS::get_singleton()->set_stdout_enabled(false);
+ OS::get_singleton()->set_stderr_enabled(false);
+}
+
+void GDScriptTest::print_handler(void *p_this, const String &p_message, bool p_error) {
+ TestResult *result = (TestResult *)p_this;
+ result->output += p_message + "\n";
+}
+
+void GDScriptTest::error_handler(void *p_this, const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_explanation, ErrorHandlerType p_type) {
+ ErrorHandlerData *data = (ErrorHandlerData *)p_this;
+ GDScriptTest *self = data->self;
+ TestResult *result = data->result;
+
+ result->status = GDTEST_RUNTIME_ERROR;
+
+ StringBuilder builder;
+ builder.append(">> ");
+ switch (p_type) {
+ case ERR_HANDLER_ERROR:
+ builder.append("ERROR");
+ break;
+ case ERR_HANDLER_WARNING:
+ builder.append("WARNING");
+ break;
+ case ERR_HANDLER_SCRIPT:
+ builder.append("SCRIPT ERROR");
+ break;
+ case ERR_HANDLER_SHADER:
+ builder.append("SHADER ERROR");
+ break;
+ default:
+ builder.append("Unknown error type");
+ break;
+ }
+
+ builder.append("\n>> on function: ");
+ builder.append(p_function);
+ builder.append("()\n>> ");
+ builder.append(String(p_file).trim_prefix(self->base_dir));
+ builder.append("\n>> ");
+ builder.append(itos(p_line));
+ builder.append("\n>> ");
+ builder.append(p_error);
+ if (strlen(p_explanation) > 0) {
+ builder.append("\n>> ");
+ builder.append(p_explanation);
+ }
+ builder.append("\n");
+
+ result->output = builder.as_string();
+}
+
+bool GDScriptTest::check_output(const String &p_output) const {
+ Error err = OK;
+ String expected = FileAccess::get_file_as_string(output_file, &err);
+
+ ERR_FAIL_COND_V_MSG(err != OK, false, "Error when opening the output file.");
+
+ String got = p_output.strip_edges(); // TODO: may be hacky.
+ got += "\n"; // Make sure to insert newline for CI static checks.
+
+ return got == expected;
+}
+
+String GDScriptTest::get_text_for_status(GDScriptTest::TestStatus p_status) const {
+ switch (p_status) {
+ case GDTEST_OK:
+ return "GDTEST_OK";
+ case GDTEST_LOAD_ERROR:
+ return "GDTEST_LOAD_ERROR";
+ case GDTEST_PARSER_ERROR:
+ return "GDTEST_PARSER_ERROR";
+ case GDTEST_ANALYZER_ERROR:
+ return "GDTEST_ANALYZER_ERROR";
+ case GDTEST_COMPILER_ERROR:
+ return "GDTEST_COMPILER_ERROR";
+ case GDTEST_RUNTIME_ERROR:
+ return "GDTEST_RUNTIME_ERROR";
+ }
+ return "";
+}
+
+GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
+ disable_stdout();
+
+ TestResult result;
+ result.status = GDTEST_OK;
+ result.output = String();
+
+ Error err = OK;
+
+ // Create script.
+ Ref<GDScript> script;
+ script.instance();
+ script->set_path(source_file);
+ script->set_script_path(source_file);
+ err = script->load_source_code(source_file);
+ if (err != OK) {
+ enable_stdout();
+ result.status = GDTEST_LOAD_ERROR;
+ result.passed = false;
+ ERR_FAIL_V_MSG(result, "\nCould not load source code for: '" + source_file + "'");
+ }
+
+ // Test parsing.
+ GDScriptParser parser;
+ err = parser.parse(script->get_source_code(), source_file, false);
+ if (err != OK) {
+ enable_stdout();
+ result.status = GDTEST_PARSER_ERROR;
+ result.output = get_text_for_status(result.status) + "\n";
+
+ const List<GDScriptParser::ParserError> &errors = parser.get_errors();
+ for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E; E = E->next()) {
+ result.output += E->get().message + "\n"; // TODO: line, column?
+ break; // Only the first error since the following might be cascading.
+ }
+ if (!p_is_generating) {
+ result.passed = check_output(result.output);
+ }
+ return result;
+ }
+
+ // Test type-checking.
+ GDScriptAnalyzer analyzer(&parser);
+ err = analyzer.analyze();
+ if (err != OK) {
+ enable_stdout();
+ result.status = GDTEST_ANALYZER_ERROR;
+ result.output = get_text_for_status(result.status) + "\n";
+
+ const List<GDScriptParser::ParserError> &errors = parser.get_errors();
+ for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E; E = E->next()) {
+ result.output += E->get().message + "\n"; // TODO: line, column?
+ break; // Only the first error since the following might be cascading.
+ }
+ if (!p_is_generating) {
+ result.passed = check_output(result.output);
+ }
+ return result;
+ }
+
+ StringBuilder warning_string;
+ for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E != nullptr; E = E->next()) {
+ const GDScriptWarning warning = E->get();
+ warning_string.append(">> WARNING");
+ warning_string.append("\n>> Line: ");
+ warning_string.append(itos(warning.start_line));
+ warning_string.append("\n>> ");
+ warning_string.append(warning.get_name());
+ warning_string.append("\n>> ");
+ warning_string.append(warning.get_message());
+ warning_string.append("\n");
+ }
+ result.output += warning_string.as_string();
+
+ // Test compiling.
+ GDScriptCompiler compiler;
+ err = compiler.compile(&parser, script.ptr(), false);
+ if (err != OK) {
+ enable_stdout();
+ result.status = GDTEST_COMPILER_ERROR;
+ result.output = get_text_for_status(result.status) + "\n";
+ result.output = compiler.get_error();
+ if (!p_is_generating) {
+ result.passed = check_output(result.output);
+ }
+ return result;
+ }
+
+ // Test running.
+ const Map<StringName, GDScriptFunction *>::Element *test_function_element = script->get_member_functions().find(GDScriptTestRunner::test_function_name);
+ if (test_function_element == nullptr) {
+ enable_stdout();
+ result.status = GDTEST_LOAD_ERROR;
+ result.output = "";
+ result.passed = false;
+ ERR_FAIL_V_MSG(result, "\nCould not find test function on: '" + source_file + "'");
+ }
+
+ script->reload();
+
+ // Create object instance for test.
+ Object *obj = ClassDB::instance(script->get_native()->get_name());
+ Ref<Reference> obj_ref;
+ if (obj->is_reference()) {
+ obj_ref = Ref<Reference>(Object::cast_to<Reference>(obj));
+ }
+ obj->set_script(script);
+ GDScriptInstance *instance = static_cast<GDScriptInstance *>(obj->get_script_instance());
+
+ // Setup output handlers.
+ ErrorHandlerData error_data(&result, this);
+
+ _print_handler.userdata = &result;
+ _error_handler.userdata = &error_data;
+ add_print_handler(&_print_handler);
+ add_error_handler(&_error_handler);
+
+ // Call test function.
+ Callable::CallError call_err;
+ instance->call(GDScriptTestRunner::test_function_name, nullptr, 0, call_err);
+
+ // Tear down output handlers.
+ remove_print_handler(&_print_handler);
+ remove_error_handler(&_error_handler);
+
+ // Check results.
+ if (call_err.error != Callable::CallError::CALL_OK) {
+ enable_stdout();
+ result.status = GDTEST_LOAD_ERROR;
+ result.passed = false;
+ ERR_FAIL_V_MSG(result, "\nCould not call test function on: '" + source_file + "'");
+ }
+
+ result.output = get_text_for_status(result.status) + "\n" + result.output;
+ if (!p_is_generating) {
+ result.passed = check_output(result.output);
+ }
+
+ if (obj_ref.is_null()) {
+ memdelete(obj);
+ }
+
+ enable_stdout();
+ return result;
+}
+
+GDScriptTest::TestResult GDScriptTest::run_test() {
+ return execute_test_code(false);
+}
+
+bool GDScriptTest::generate_output() {
+ TestResult result = execute_test_code(true);
+ if (result.status == GDTEST_LOAD_ERROR) {
+ return false;
+ }
+
+ Error err = OK;
+ FileAccessRef out_file = FileAccess::open(output_file, FileAccess::WRITE, &err);
+ if (err != OK) {
+ return false;
+ }
+
+ String output = result.output.strip_edges(); // TODO: may be hacky.
+ output += "\n"; // Make sure to insert newline for CI static checks.
+
+ out_file->store_string(output);
+ out_file->close();
+
+ return true;
+}
+
+} // namespace GDScriptTests
diff --git a/modules/gdscript/tests/gdscript_test_runner.h b/modules/gdscript/tests/gdscript_test_runner.h
new file mode 100644
index 0000000000..9b2d14a371
--- /dev/null
+++ b/modules/gdscript/tests/gdscript_test_runner.h
@@ -0,0 +1,126 @@
+/*************************************************************************/
+/* gdscript_test_runner.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GDSCRIPT_TEST_H
+#define GDSCRIPT_TEST_H
+
+#include "../gdscript.h"
+#include "core/error/error_macros.h"
+#include "core/string/print_string.h"
+#include "core/string/ustring.h"
+#include "core/templates/vector.h"
+
+namespace GDScriptTests {
+
+void init_autoloads();
+void init_language(const String &p_base_path);
+void finish_language();
+
+// Single test instance in a suite.
+class GDScriptTest {
+public:
+ enum TestStatus {
+ GDTEST_OK,
+ GDTEST_LOAD_ERROR,
+ GDTEST_PARSER_ERROR,
+ GDTEST_ANALYZER_ERROR,
+ GDTEST_COMPILER_ERROR,
+ GDTEST_RUNTIME_ERROR,
+ };
+
+ struct TestResult {
+ TestStatus status;
+ String output;
+ bool passed;
+ };
+
+private:
+ struct ErrorHandlerData {
+ TestResult *result;
+ GDScriptTest *self;
+ ErrorHandlerData(TestResult *p_result, GDScriptTest *p_this) {
+ result = p_result;
+ self = p_this;
+ }
+ };
+
+ String source_file;
+ String output_file;
+ String base_dir;
+
+ PrintHandlerList _print_handler;
+ ErrorHandlerList _error_handler;
+
+ void enable_stdout();
+ void disable_stdout();
+ bool check_output(const String &p_output) const;
+ String get_text_for_status(TestStatus p_status) const;
+
+ TestResult execute_test_code(bool p_is_generating);
+
+public:
+ static void print_handler(void *p_this, const String &p_message, bool p_error);
+ static void error_handler(void *p_this, const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_explanation, ErrorHandlerType p_type);
+ TestResult run_test();
+ bool generate_output();
+
+ const String &get_source_file() const { return source_file; }
+ const String &get_output_file() const { return output_file; }
+
+ GDScriptTest(const String &p_source_path, const String &p_output_path, const String &p_base_dir);
+ GDScriptTest() :
+ GDScriptTest(String(), String(), String()) {} // Needed to use in Vector.
+};
+
+class GDScriptTestRunner {
+ String source_dir;
+ Vector<GDScriptTest> tests;
+
+ bool is_generating = false;
+ bool do_init_languages = false;
+
+ bool make_tests();
+ bool make_tests_for_dir(const String &p_dir);
+ bool generate_class_index();
+
+public:
+ static StringName test_function_name;
+
+ static void handle_cmdline();
+ int run_tests();
+ bool generate_outputs();
+
+ GDScriptTestRunner(const String &p_source_dir, bool p_init_language);
+ ~GDScriptTestRunner();
+};
+
+} // namespace GDScriptTests
+
+#endif // GDSCRIPT_TEST_H
diff --git a/modules/gdscript/tests/gdscript_test_runner_suite.h b/modules/gdscript/tests/gdscript_test_runner_suite.h
new file mode 100644
index 0000000000..8fd77239cd
--- /dev/null
+++ b/modules/gdscript/tests/gdscript_test_runner_suite.h
@@ -0,0 +1,74 @@
+/*************************************************************************/
+/* gdscript_test_runner_suite.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GDSCRIPT_TEST_RUNNER_SUITE_H
+#define GDSCRIPT_TEST_RUNNER_SUITE_H
+
+#include "gdscript_test_runner.h"
+#include "tests/test_macros.h"
+
+namespace GDScriptTests {
+
+TEST_SUITE("[Modules][GDScript]") {
+ // GDScript 2.0 is still under heavy construction.
+ // Allow the tests to fail, but do not ignore errors during development.
+ // Update the scripts and expected output as needed.
+ TEST_CASE("Script compilation and runtime") {
+ GDScriptTestRunner runner("modules/gdscript/tests/scripts", true);
+ int fail_count = runner.run_tests();
+ INFO("Make sure `*.out` files have expected results.");
+ REQUIRE_MESSAGE(fail_count == 0, "All GDScript tests should pass.");
+ }
+}
+
+TEST_CASE("[Modules][GDScript] Load source code dynamically and run it") {
+ Ref<GDScript> gdscript = memnew(GDScript);
+ gdscript->set_source_code(R"(
+extends Reference
+
+func _init():
+ set_meta("result", 42)
+)");
+ // A spurious `Condition "err" is true` message is printed (despite parsing being successful and returning `OK`).
+ // Silence it.
+ ERR_PRINT_OFF;
+ const Error error = gdscript->reload();
+ ERR_PRINT_ON;
+ CHECK_MESSAGE(error == OK, "The script should parse successfully.");
+
+ // Run the script by assigning it to a reference-counted object.
+ Ref<Reference> reference = memnew(Reference);
+ reference->set_script(gdscript);
+ CHECK_MESSAGE(int(reference->get_meta("result")) == 42, "The script should assign object metadata successfully.");
+}
+
+} // namespace GDScriptTests
+
+#endif // GDSCRIPT_TEST_RUNNER_SUITE_H
diff --git a/modules/gdscript/tests/scripts/.gitignore b/modules/gdscript/tests/scripts/.gitignore
new file mode 100644
index 0000000000..94c5b1bf6b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/.gitignore
@@ -0,0 +1,2 @@
+# Ignore metadata if someone open this on Godot.
+/.godot
diff --git a/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.gd b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.gd
new file mode 100644
index 0000000000..d21d8bce96
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.gd
@@ -0,0 +1,9 @@
+extends Node
+
+func test():
+ set_name("TestNodeName")
+ if get_name() == &"TestNodeName":
+ print("Name is equal")
+ else:
+ print("Name is not equal")
+ print(get_name() is StringName)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out
new file mode 100644
index 0000000000..dc4348d9c3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+Name is equal
+True
diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_argument.gd b/modules/gdscript/tests/scripts/parser/errors/missing_argument.gd
new file mode 100644
index 0000000000..c56ad94095
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/missing_argument.gd
@@ -0,0 +1,6 @@
+func args(a, b):
+ print(a)
+ print(b)
+
+func test():
+ args(1,)
diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_argument.out b/modules/gdscript/tests/scripts/parser/errors/missing_argument.out
new file mode 100644
index 0000000000..fc2a891109
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/missing_argument.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Too few arguments for "args()" call. Expected at least 2 but received 1.
diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd b/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd
new file mode 100644
index 0000000000..a1077e1985
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd
@@ -0,0 +1,2 @@
+func test():
+ var a = ("missing paren ->"
diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.out b/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.out
new file mode 100644
index 0000000000..7326afa33d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected closing ")" after grouping expression.
diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd b/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd
new file mode 100644
index 0000000000..62cb633e9e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd
@@ -0,0 +1,3 @@
+func test():
+ if true # Missing colon here.
+ print("true")
diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_colon.out b/modules/gdscript/tests/scripts/parser/errors/missing_colon.out
new file mode 100644
index 0000000000..687b963bc8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/missing_colon.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected ":" after "if" condition.
diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd b/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd
new file mode 100644
index 0000000000..116b0151da
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd
@@ -0,0 +1,6 @@
+func args(a, b):
+ print(a)
+ print(b)
+
+func test():
+ args(1,2
diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.out b/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.out
new file mode 100644
index 0000000000..34ea7ac323
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected closing ")" after call arguments.
diff --git a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.gd b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.gd
new file mode 100644
index 0000000000..9ad77f1432
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.gd
@@ -0,0 +1,3 @@
+func test():
+ print("Using spaces")
+ print("Using tabs")
diff --git a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out
new file mode 100644
index 0000000000..6390de9788
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Used "\t" for indentation instead " " as used before in the file.
diff --git a/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd b/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd
new file mode 100644
index 0000000000..3875ce3936
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd
@@ -0,0 +1,3 @@
+extends Node
+func test():
+ var a = $ # Expected some node path.
diff --git a/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.out b/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.out
new file mode 100644
index 0000000000..b3dc181a22
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expect node path as string or identifier after "$".
diff --git a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd
new file mode 100644
index 0000000000..6fd2692d47
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd
@@ -0,0 +1,3 @@
+extends Node
+func test():
+ $23 # Can't use number here.
diff --git a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.out b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.out
new file mode 100644
index 0000000000..b3dc181a22
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expect node path as string or identifier after "$".
diff --git a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd
new file mode 100644
index 0000000000..1836d42226
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd
@@ -0,0 +1,3 @@
+extends Node
+func test():
+ $MyNode/23 # Can't use number here.
diff --git a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.out b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.out
new file mode 100644
index 0000000000..dcb4ccecb0
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expect node path after "/".
diff --git a/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd
new file mode 100644
index 0000000000..08f2eedb2d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd
@@ -0,0 +1,2 @@
+func test():
+ print("A"); print("B")
diff --git a/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out
new file mode 100644
index 0000000000..fc03f3efe8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+A
+B
diff --git a/modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.gd b/modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.gd
new file mode 100644
index 0000000000..6097b11b10
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.gd
@@ -0,0 +1,7 @@
+# See https://github.com/godotengine/godot/issues/41066.
+
+func f(p, ): ## <-- no errors
+ print(p)
+
+func test():
+ f(0, ) ## <-- no error
diff --git a/modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.out b/modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.out
new file mode 100644
index 0000000000..94e2ec2af8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+0
diff --git a/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd b/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd
new file mode 100644
index 0000000000..3b48f10ca7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd
@@ -0,0 +1,12 @@
+var a # No init.
+var b = 42 # Init.
+
+func test():
+ var c # No init, local.
+ var d = 23 # Init, local.
+
+ a = 1
+ c = 2
+
+ prints(a, b, c, d)
+ print("OK")
diff --git a/modules/gdscript/tests/scripts/parser/features/variable_declaration.out b/modules/gdscript/tests/scripts/parser/features/variable_declaration.out
new file mode 100644
index 0000000000..2e0a63c024
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/variable_declaration.out
@@ -0,0 +1,7 @@
+GDTEST_OK
+>> WARNING
+>> Line: 5
+>> UNASSIGNED_VARIABLE
+>> The variable 'c' was used but never assigned a value.
+1 42 2 23
+OK
diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd
new file mode 100644
index 0000000000..68e3bd424f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd
@@ -0,0 +1,2 @@
+func test():
+ var unused = "not used"
diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_variable.out b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.out
new file mode 100644
index 0000000000..270e0e69c0
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+>> WARNING
+>> Line: 2
+>> UNUSED_VARIABLE
+>> The local variable 'unused' is declared but never used in the block. If this is intended, prefix it with an underscore: '_unused'
diff --git a/modules/gdscript/tests/scripts/project.godot b/modules/gdscript/tests/scripts/project.godot
new file mode 100644
index 0000000000..25b49c0abd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/project.godot
@@ -0,0 +1,10 @@
+; This is not an actual project.
+; This config only exists to properly set up the test environment.
+; It also helps for opening Godot to edit the scripts, but please don't
+; let the editor changes be saved.
+
+config_version=4
+
+[application]
+
+config/name="GDScript Integration Test Suite"
diff --git a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd
new file mode 100644
index 0000000000..10780b5379
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd
@@ -0,0 +1,5 @@
+func test():
+ var node := Node.new()
+ var inside_tree = node.is_inside_tree
+ node.free()
+ inside_tree.call()
diff --git a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out
new file mode 100644
index 0000000000..e585c374e2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/callable_call_after_free_object.gd
+>> 5
+>> Attempt to call function 'null::is_inside_tree (Callable)' on a null instance.
diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp
index 3cc0eee672..fc73b5bb72 100644
--- a/modules/gdscript/tests/test_gdscript.cpp
+++ b/modules/gdscript/tests/test_gdscript.cpp
@@ -47,7 +47,7 @@
#include "editor/editor_settings.h"
#endif
-namespace TestGDScript {
+namespace GDScriptTests {
static void test_tokenizer(const String &p_code, const Vector<String> &p_lines) {
GDScriptTokenizer tokenizer;
@@ -66,7 +66,7 @@ static void test_tokenizer(const String &p_code, const Vector<String> &p_lines)
StringBuilder token;
token += " --> "; // Padding for line number.
- for (int l = current.start_line; l <= current.end_line; l++) {
+ for (int l = current.start_line; l <= current.end_line && l <= p_lines.size(); l++) {
print_line(vformat("%04d %s", l, p_lines[l - 1]).replace("\t", tab));
}
@@ -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);
@@ -183,60 +195,6 @@ static void test_compiler(const String &p_code, const String &p_script_path, con
}
}
-void init_autoloads() {
- Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
-
- // First pass, add the constants so they exist before any script is loaded.
- for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
- const ProjectSettings::AutoloadInfo &info = E->get();
-
- if (info.is_singleton) {
- for (int i = 0; i < ScriptServer::get_language_count(); i++) {
- ScriptServer::get_language(i)->add_global_constant(info.name, Variant());
- }
- }
- }
-
- // Second pass, load into global constants.
- for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
- const ProjectSettings::AutoloadInfo &info = E->get();
-
- if (!info.is_singleton) {
- // Skip non-singletons since we don't have a scene tree here anyway.
- continue;
- }
-
- RES res = ResourceLoader::load(info.path);
- ERR_CONTINUE_MSG(res.is_null(), "Can't autoload: " + info.path);
- Node *n = nullptr;
- if (res->is_class("PackedScene")) {
- Ref<PackedScene> ps = res;
- n = ps->instance();
- } else if (res->is_class("Script")) {
- Ref<Script> script_res = res;
- StringName ibt = script_res->get_instance_base_type();
- bool valid_type = ClassDB::is_parent_class(ibt, "Node");
- ERR_CONTINUE_MSG(!valid_type, "Script does not inherit a Node: " + info.path);
-
- Object *obj = ClassDB::instance(ibt);
-
- ERR_CONTINUE_MSG(obj == nullptr,
- "Cannot instance script for autoload, expected 'Node' inheritance, got: " +
- String(ibt));
-
- n = Object::cast_to<Node>(obj);
- n->set_script(script_res);
- }
-
- ERR_CONTINUE_MSG(!n, "Path in autoload not a node or script: " + info.path);
- n->set_name(info.name);
-
- for (int i = 0; i < ScriptServer::get_language_count(); i++) {
- ScriptServer::get_language(i)->add_global_constant(info.name, n);
- }
- }
-}
-
void test(TestType p_type) {
List<String> cmdlargs = OS::get_singleton()->get_cmdline_args();
@@ -253,24 +211,12 @@ void test(TestType p_type) {
FileAccessRef fa = FileAccess::open(test, FileAccess::READ);
ERR_FAIL_COND_MSG(!fa, "Could not open file: " + test);
- // Init PackedData since it's used by ProjectSettings.
- PackedData *packed_data = memnew(PackedData);
-
- // Setup project settings since it's needed by the languages to get the global scripts.
- // This also sets up the base resource path.
- Error err = ProjectSettings::get_singleton()->setup(fa->get_path_absolute().get_base_dir(), String(), true);
- if (err) {
- print_line("Could not load project settings.");
- // Keep going since some scripts still work without this.
- }
-
// Initialize the language for the test routine.
- ScriptServer::init_languages();
- init_autoloads();
+ init_language(fa->get_path_absolute().get_base_dir());
Vector<uint8_t> buf;
- int flen = fa->get_len();
- buf.resize(fa->get_len() + 1);
+ uint64_t flen = fa->get_length();
+ buf.resize(flen + 1);
fa->get_buffer(buf.ptrw(), flen);
buf.write[flen] = 0;
@@ -300,8 +246,6 @@ void test(TestType p_type) {
print_line("Not implemented.");
}
- // Destroy stuff we set up earlier.
- ScriptServer::finish_languages();
- memdelete(packed_data);
+ finish_language();
}
-} // namespace TestGDScript
+} // namespace GDScriptTests
diff --git a/modules/gdscript/tests/test_gdscript.h b/modules/gdscript/tests/test_gdscript.h
index bbda46cdad..c7ee5a2208 100644
--- a/modules/gdscript/tests/test_gdscript.h
+++ b/modules/gdscript/tests/test_gdscript.h
@@ -31,7 +31,10 @@
#ifndef TEST_GDSCRIPT_H
#define TEST_GDSCRIPT_H
-namespace TestGDScript {
+#include "gdscript_test_runner.h"
+#include "tests/test_macros.h"
+
+namespace GDScriptTests {
enum TestType {
TEST_TOKENIZER,
@@ -41,6 +44,7 @@ enum TestType {
};
void test(TestType p_type);
-} // namespace TestGDScript
+
+} // namespace GDScriptTests
#endif // TEST_GDSCRIPT_H