summaryrefslogtreecommitdiffstats
path: root/modules/gdscript
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript')
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp6
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp364
-rw-r--r--modules/gdscript/gdscript_analyzer.h4
-rw-r--r--modules/gdscript/gdscript_parser.cpp10
-rw-r--r--modules/gdscript/gdscript_parser.h4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_call_arg.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_call_arg.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_param.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_param.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_before_declared.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_before_declared.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_const.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_const.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_type.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/lambda_cyclic_ref_body.gd34
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/lambda_cyclic_ref_body.out5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.gd41
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.notest.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.out11
20 files changed, 365 insertions, 155 deletions
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index 862799a6ec..e488d6e266 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -304,7 +304,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
// Allow ABCDEF in hex notation.
if (is_hex_notation && (is_hex_digit(str[j]) || is_a_digit)) {
is_a_digit = true;
- } else {
+ } else if (str[j] != '_') {
is_hex_notation = false;
}
@@ -327,14 +327,14 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
} else if (!((str[j] == '-' || str[j] == '+') && str[j - 1] == 'e' && !prev_is_digit) &&
!(str[j] == '_' && (prev_is_digit || str[j - 1] == 'b' || str[j - 1] == 'x' || str[j - 1] == '.')) &&
!(str[j] == 'e' && (prev_is_digit || str[j - 1] == '_')) &&
- !(str[j] == '.' && (prev_is_digit || (!prev_is_binary_op && (j > 0 && (str[j - 1] == '-' || str[j - 1] == '+' || str[j - 1] == '~'))))) &&
+ !(str[j] == '.' && (prev_is_digit || (!prev_is_binary_op && (j > 0 && (str[j - 1] == '_' || str[j - 1] == '-' || str[j - 1] == '+' || str[j - 1] == '~'))))) &&
!((str[j] == '-' || str[j] == '+' || str[j] == '~') && !is_binary_op && !prev_is_binary_op && str[j - 1] != 'e')) {
/* This condition continues number highlighting in special cases.
1st row: '+' or '-' after scientific notation (like 3e-4);
2nd row: '_' as a numeric separator;
3rd row: Scientific notation 'e' and floating points;
4th row: Floating points inside the number, or leading if after a unary mathematical operator;
- 5th row: Multiple unary mathematical operators (like ~-7)*/
+ 5th row: Multiple unary mathematical operators (like ~-7) */
in_number = false;
}
} else if (str[j] == '.' && !is_binary_op && is_digit(str[j + 1]) && (j == 0 || (j > 0 && str[j - 1] != '.'))) {
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 214b484b12..18c69467dc 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -613,144 +613,183 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
return result;
}
- StringName first = p_type->type_chain[0]->name;
-
- if (first == SNAME("Variant")) {
- if (p_type->type_chain.size() == 2) {
- // May be nested enum.
- StringName enum_name = p_type->type_chain[1]->name;
- StringName qualified_name = String(first) + ENUM_SEPARATOR + String(p_type->type_chain[1]->name);
- if (CoreConstants::is_global_enum(qualified_name)) {
- result = make_global_enum_type(enum_name, first, true);
- return result;
+ const GDScriptParser::IdentifierNode *first_id = p_type->type_chain[0];
+ StringName first = first_id->name;
+ bool type_found = false;
+
+ if (first_id->suite && first_id->suite->has_local(first)) {
+ const GDScriptParser::SuiteNode::Local &local = first_id->suite->get_local(first);
+ if (local.type == GDScriptParser::SuiteNode::Local::CONSTANT) {
+ result = local.get_datatype();
+ if (!result.is_set()) {
+ // Don't try to resolve it as the constant can be declared below.
+ push_error(vformat(R"(Local constant "%s" is not resolved at this point.)", first), first_id);
+ return bad_type;
+ }
+ if (result.is_meta_type) {
+ type_found = true;
+ } else if (Ref<Script>(local.constant->initializer->reduced_value).is_valid()) {
+ Ref<GDScript> gdscript = local.constant->initializer->reduced_value;
+ if (gdscript.is_valid()) {
+ Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_script_path());
+ if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
+ push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), first_id);
+ return bad_type;
+ }
+ result = ref->get_parser()->head->get_datatype();
+ } else {
+ result = make_script_meta_type(local.constant->initializer->reduced_value);
+ }
+ type_found = true;
} else {
- push_error(vformat(R"(Name "%s" is not a nested type of "Variant".)", enum_name), p_type->type_chain[1]);
+ push_error(vformat(R"(Local constant "%s" is not a valid type.)", first), first_id);
return bad_type;
}
- } else if (p_type->type_chain.size() > 2) {
- push_error(R"(Variant only contains enum types, which do not have nested types.)", p_type->type_chain[2]);
- return bad_type;
- }
- result.kind = GDScriptParser::DataType::VARIANT;
- } else if (first == SNAME("Object")) {
- // Object is treated like a native type, not a built-in.
- result.kind = GDScriptParser::DataType::NATIVE;
- result.builtin_type = Variant::OBJECT;
- result.native_type = SNAME("Object");
- } else if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) {
- // Built-in types.
- if (p_type->type_chain.size() > 1) {
- push_error(R"(Built-in types don't contain nested types.)", p_type->type_chain[1]);
+ } else {
+ push_error(vformat(R"(Local %s "%s" cannot be used as a type.)", local.get_name(), first), first_id);
return bad_type;
}
- result.kind = GDScriptParser::DataType::BUILTIN;
- result.builtin_type = GDScriptParser::get_builtin_type(first);
+ }
- if (result.builtin_type == Variant::ARRAY) {
- GDScriptParser::DataType container_type = type_from_metatype(resolve_datatype(p_type->container_type));
- if (container_type.kind != GDScriptParser::DataType::VARIANT) {
- container_type.is_constant = false;
- result.set_container_element_type(container_type);
- }
- }
- } else if (class_exists(first)) {
- // Native engine classes.
- result.kind = GDScriptParser::DataType::NATIVE;
- result.builtin_type = Variant::OBJECT;
- result.native_type = first;
- } else if (ScriptServer::is_global_class(first)) {
- if (parser->script_path == ScriptServer::get_global_class_path(first)) {
- result = parser->head->get_datatype();
- } else {
- String path = ScriptServer::get_global_class_path(first);
- String ext = path.get_extension();
- if (ext == GDScriptLanguage::get_singleton()->get_extension()) {
- Ref<GDScriptParserRef> ref = get_parser_for(path);
- if (!ref.is_valid() || ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
- push_error(vformat(R"(Could not parse global class "%s" from "%s".)", first, ScriptServer::get_global_class_path(first)), p_type);
+ if (!type_found) {
+ if (first == SNAME("Variant")) {
+ if (p_type->type_chain.size() == 2) {
+ // May be nested enum.
+ StringName enum_name = p_type->type_chain[1]->name;
+ StringName qualified_name = String(first) + ENUM_SEPARATOR + String(p_type->type_chain[1]->name);
+ if (CoreConstants::is_global_enum(qualified_name)) {
+ result = make_global_enum_type(enum_name, first, true);
+ return result;
+ } else {
+ push_error(vformat(R"(Name "%s" is not a nested type of "Variant".)", enum_name), p_type->type_chain[1]);
return bad_type;
}
- result = ref->get_parser()->head->get_datatype();
- } else {
- result = make_script_meta_type(ResourceLoader::load(path, "Script"));
+ } else if (p_type->type_chain.size() > 2) {
+ push_error(R"(Variant only contains enum types, which do not have nested types.)", p_type->type_chain[2]);
+ return bad_type;
}
- }
- } else if (ProjectSettings::get_singleton()->has_autoload(first) && ProjectSettings::get_singleton()->get_autoload(first).is_singleton) {
- const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(first);
- Ref<GDScriptParserRef> ref = get_parser_for(autoload.path);
- if (ref.is_null()) {
- push_error(vformat(R"(The referenced autoload "%s" (from "%s") could not be loaded.)", first, autoload.path), p_type);
- return bad_type;
- }
- if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
- push_error(vformat(R"(Could not parse singleton "%s" from "%s".)", first, autoload.path), p_type);
- return bad_type;
- }
- result = ref->get_parser()->head->get_datatype();
- } else if (ClassDB::has_enum(parser->current_class->base_type.native_type, first)) {
- // Native enum in current class.
- result = make_native_enum_type(first, parser->current_class->base_type.native_type);
- } else if (CoreConstants::is_global_enum(first)) {
- if (p_type->type_chain.size() > 1) {
- push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[1]);
- return bad_type;
- }
- result = make_global_enum_type(first, StringName());
- } else {
- // Classes in current scope.
- List<GDScriptParser::ClassNode *> script_classes;
- bool found = false;
- get_class_node_current_scope_classes(parser->current_class, &script_classes);
- for (GDScriptParser::ClassNode *script_class : script_classes) {
- if (found) {
- break;
+ result.kind = GDScriptParser::DataType::VARIANT;
+ } else if (first == SNAME("Object")) {
+ // Object is treated like a native type, not a built-in.
+ result.kind = GDScriptParser::DataType::NATIVE;
+ result.builtin_type = Variant::OBJECT;
+ result.native_type = SNAME("Object");
+ } else if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) {
+ // Built-in types.
+ if (p_type->type_chain.size() > 1) {
+ push_error(R"(Built-in types don't contain nested types.)", p_type->type_chain[1]);
+ return bad_type;
}
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ result.builtin_type = GDScriptParser::get_builtin_type(first);
- if (script_class->identifier && script_class->identifier->name == first) {
- result = script_class->get_datatype();
- break;
+ if (result.builtin_type == Variant::ARRAY) {
+ GDScriptParser::DataType container_type = type_from_metatype(resolve_datatype(p_type->container_type));
+ if (container_type.kind != GDScriptParser::DataType::VARIANT) {
+ container_type.is_constant = false;
+ result.set_container_element_type(container_type);
+ }
}
- if (script_class->members_indices.has(first)) {
- resolve_class_member(script_class, first, p_type);
+ } else if (class_exists(first)) {
+ // Native engine classes.
+ result.kind = GDScriptParser::DataType::NATIVE;
+ result.builtin_type = Variant::OBJECT;
+ result.native_type = first;
+ } else if (ScriptServer::is_global_class(first)) {
+ if (parser->script_path == ScriptServer::get_global_class_path(first)) {
+ result = parser->head->get_datatype();
+ } else {
+ String path = ScriptServer::get_global_class_path(first);
+ String ext = path.get_extension();
+ if (ext == GDScriptLanguage::get_singleton()->get_extension()) {
+ Ref<GDScriptParserRef> ref = get_parser_for(path);
+ if (!ref.is_valid() || ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
+ push_error(vformat(R"(Could not parse global class "%s" from "%s".)", first, ScriptServer::get_global_class_path(first)), p_type);
+ return bad_type;
+ }
+ result = ref->get_parser()->head->get_datatype();
+ } else {
+ result = make_script_meta_type(ResourceLoader::load(path, "Script"));
+ }
+ }
+ } else if (ProjectSettings::get_singleton()->has_autoload(first) && ProjectSettings::get_singleton()->get_autoload(first).is_singleton) {
+ const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(first);
+ Ref<GDScriptParserRef> ref = get_parser_for(autoload.path);
+ if (ref.is_null()) {
+ push_error(vformat(R"(The referenced autoload "%s" (from "%s") could not be loaded.)", first, autoload.path), p_type);
+ return bad_type;
+ }
+ if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
+ push_error(vformat(R"(Could not parse singleton "%s" from "%s".)", first, autoload.path), p_type);
+ return bad_type;
+ }
+ result = ref->get_parser()->head->get_datatype();
+ } else if (ClassDB::has_enum(parser->current_class->base_type.native_type, first)) {
+ // Native enum in current class.
+ result = make_native_enum_type(first, parser->current_class->base_type.native_type);
+ } else if (CoreConstants::is_global_enum(first)) {
+ if (p_type->type_chain.size() > 1) {
+ push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[1]);
+ return bad_type;
+ }
+ result = make_global_enum_type(first, StringName());
+ } else {
+ // Classes in current scope.
+ List<GDScriptParser::ClassNode *> script_classes;
+ bool found = false;
+ get_class_node_current_scope_classes(parser->current_class, &script_classes);
+ for (GDScriptParser::ClassNode *script_class : script_classes) {
+ if (found) {
+ break;
+ }
- GDScriptParser::ClassNode::Member member = script_class->get_member(first);
- switch (member.type) {
- case GDScriptParser::ClassNode::Member::CLASS:
- result = member.get_datatype();
- found = true;
- break;
- case GDScriptParser::ClassNode::Member::ENUM:
- result = member.get_datatype();
- found = true;
- break;
- case GDScriptParser::ClassNode::Member::CONSTANT:
- if (member.get_datatype().is_meta_type) {
+ if (script_class->identifier && script_class->identifier->name == first) {
+ result = script_class->get_datatype();
+ break;
+ }
+ if (script_class->members_indices.has(first)) {
+ resolve_class_member(script_class, first, p_type);
+
+ GDScriptParser::ClassNode::Member member = script_class->get_member(first);
+ switch (member.type) {
+ case GDScriptParser::ClassNode::Member::CLASS:
result = member.get_datatype();
found = true;
break;
- } else if (Ref<Script>(member.constant->initializer->reduced_value).is_valid()) {
- Ref<GDScript> gdscript = member.constant->initializer->reduced_value;
- if (gdscript.is_valid()) {
- Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_script_path());
- if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
- push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), p_type);
- return bad_type;
- }
- result = ref->get_parser()->head->get_datatype();
- } else {
- result = make_script_meta_type(member.constant->initializer->reduced_value);
- }
+ case GDScriptParser::ClassNode::Member::ENUM:
+ result = member.get_datatype();
found = true;
break;
- }
- [[fallthrough]];
- default:
- push_error(vformat(R"("%s" is a %s but does not contain a type.)", first, member.get_type_name()), p_type);
- return bad_type;
+ case GDScriptParser::ClassNode::Member::CONSTANT:
+ if (member.get_datatype().is_meta_type) {
+ result = member.get_datatype();
+ found = true;
+ break;
+ } else if (Ref<Script>(member.constant->initializer->reduced_value).is_valid()) {
+ Ref<GDScript> gdscript = member.constant->initializer->reduced_value;
+ if (gdscript.is_valid()) {
+ Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_script_path());
+ if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
+ push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), p_type);
+ return bad_type;
+ }
+ result = ref->get_parser()->head->get_datatype();
+ } else {
+ result = make_script_meta_type(member.constant->initializer->reduced_value);
+ }
+ found = true;
+ break;
+ }
+ [[fallthrough]];
+ default:
+ push_error(vformat(R"("%s" is a %s but does not contain a type.)", first, member.get_type_name()), p_type);
+ return bad_type;
+ }
}
}
}
}
+
if (!result.is_set()) {
push_error(vformat(R"(Could not find type "%s" in the current scope.)", first), p_type);
return bad_type;
@@ -882,9 +921,12 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
case GDScriptParser::ClassNode::Member::VARIABLE: {
bool previous_static_context = static_context;
static_context = member.variable->is_static;
+
check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable);
+
member.variable->set_datatype(resolving_datatype);
resolve_variable(member.variable, false);
+ resolve_pending_lambda_bodies();
// Apply annotations.
for (GDScriptParser::AnnotationNode *&E : member.variable->annotations) {
@@ -893,7 +935,9 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
E->apply(parser, member.variable);
}
}
+
static_context = previous_static_context;
+
#ifdef DEBUG_ENABLED
if (member.variable->exported && member.variable->onready) {
parser->push_warning(member.variable, GDScriptWarning::ONREADY_WITH_EXPORT);
@@ -1345,6 +1389,11 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
}
}
+ if (!pending_body_resolution_lambdas.is_empty()) {
+ ERR_PRINT("GDScript bug (please report): Not all pending lambda bodies were resolved in time.");
+ resolve_pending_lambda_bodies();
+ }
+
parser->current_class = previous_class;
}
@@ -1757,6 +1806,7 @@ void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) {
#endif // DEBUG_ENABLED
resolve_node(stmt);
+ resolve_pending_lambda_bodies();
#ifdef DEBUG_ENABLED
parser->ignored_warnings = previously_ignored_warnings;
@@ -3080,7 +3130,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
base_type.is_meta_type = false;
is_self = true;
- if (p_call->callee == nullptr && !lambda_stack.is_empty()) {
+ if (p_call->callee == nullptr && current_lambda != nullptr) {
push_error("Cannot use `super()` inside a lambda.", p_call);
}
} else if (callee_type == GDScriptParser::Node::IDENTIFIER) {
@@ -3753,7 +3803,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
}
}
- if (!lambda_stack.is_empty()) {
+ if (current_lambda != nullptr) {
// If the identifier is a member variable (including the native class properties) or a signal, we consider the lambda to be using `self`, so we keep a reference to the current instance.
if (source_is_variable || source_is_signal) {
mark_lambda_use_self();
@@ -3765,7 +3815,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
return;
}
- GDScriptParser::FunctionNode *function_test = lambda_stack.back()->get()->function;
+ GDScriptParser::FunctionNode *function_test = current_lambda->function;
// Make sure we aren't capturing variable in the same lambda.
// This also add captures for nested lambdas.
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)) {
@@ -3920,34 +3970,12 @@ void GDScriptAnalyzer::reduce_lambda(GDScriptParser::LambdaNode *p_lambda) {
return;
}
- lambda_stack.push_back(p_lambda);
+ GDScriptParser::LambdaNode *previous_lambda = current_lambda;
+ current_lambda = p_lambda;
resolve_function_signature(p_lambda->function, p_lambda, true);
- resolve_function_body(p_lambda->function, true);
- lambda_stack.pop_back();
-
- 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;
- }
+ current_lambda = previous_lambda;
- // 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;
- }
- }
+ pending_body_resolution_lambdas.push_back(p_lambda);
}
void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) {
@@ -5288,9 +5316,53 @@ void GDScriptAnalyzer::downgrade_node_type_source(GDScriptParser::Node *p_node)
}
void GDScriptAnalyzer::mark_lambda_use_self() {
- for (GDScriptParser::LambdaNode *lambda : lambda_stack) {
+ GDScriptParser::LambdaNode *lambda = current_lambda;
+ while (lambda != nullptr) {
lambda->use_self = true;
+ lambda = lambda->parent_lambda;
+ }
+}
+
+void GDScriptAnalyzer::resolve_pending_lambda_bodies() {
+ if (pending_body_resolution_lambdas.is_empty()) {
+ return;
+ }
+
+ GDScriptParser::LambdaNode *previous_lambda = current_lambda;
+
+ List<GDScriptParser::LambdaNode *> lambdas = pending_body_resolution_lambdas;
+ pending_body_resolution_lambdas.clear();
+
+ for (GDScriptParser::LambdaNode *lambda : lambdas) {
+ current_lambda = lambda;
+ resolve_function_body(lambda->function, true);
+
+ int captures_amount = lambda->captures.size();
+ if (captures_amount > 0) {
+ // Create space for lambda parameters.
+ // At the beginning to not mess with optional parameters.
+ int param_count = lambda->function->parameters.size();
+ lambda->function->parameters.resize(param_count + captures_amount);
+ for (int i = param_count - 1; i >= 0; i--) {
+ lambda->function->parameters.write[i + captures_amount] = lambda->function->parameters[i];
+ lambda->function->parameters_indices[lambda->function->parameters[i]->identifier->name] = i + captures_amount;
+ }
+
+ // Add captures as extra parameters at the beginning.
+ for (int i = 0; i < lambda->captures.size(); i++) {
+ GDScriptParser::IdentifierNode *capture = 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());
+
+ lambda->function->parameters.write[i] = capture_param;
+ lambda->function->parameters_indices[capture->name] = i;
+ }
+ }
}
+
+ current_lambda = previous_lambda;
}
bool GDScriptAnalyzer::class_exists(const StringName &p_class) const {
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 5bc2c89a87..ec155706df 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -43,7 +43,8 @@ class GDScriptAnalyzer {
HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
const GDScriptParser::EnumNode *current_enum = nullptr;
- List<GDScriptParser::LambdaNode *> lambda_stack;
+ GDScriptParser::LambdaNode *current_lambda = nullptr;
+ List<GDScriptParser::LambdaNode *> pending_body_resolution_lambdas;
bool static_context = false;
// Tests for detecting invalid overloading of script members
@@ -129,6 +130,7 @@ class GDScriptAnalyzer {
void mark_node_unsafe(const GDScriptParser::Node *p_node);
void downgrade_node_type_source(GDScriptParser::Node *p_node);
void mark_lambda_use_self();
+ void resolve_pending_lambda_bodies();
bool class_exists(const StringName &p_class) const;
Ref<GDScriptParserRef> get_parser_for(const String &p_path);
void reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype);
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 1dde67d2d1..0f7166c101 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -2291,9 +2291,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode
IdentifierNode *identifier = alloc_node<IdentifierNode>();
complete_extents(identifier);
identifier->name = previous.get_identifier();
-#ifdef DEBUG_ENABLED
identifier->suite = current_suite;
-#endif
if (current_suite != nullptr && current_suite->has_local(identifier->name)) {
const SuiteNode::Local &declaration = current_suite->get_local(identifier->name);
@@ -3151,6 +3149,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_
GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign) {
LambdaNode *lambda = alloc_node<LambdaNode>();
lambda->parent_function = current_function;
+ lambda->parent_lambda = current_lambda;
+
FunctionNode *function = alloc_node<FunctionNode>();
function->source_lambda = lambda;
@@ -3178,6 +3178,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
FunctionNode *previous_function = current_function;
current_function = function;
+ LambdaNode *previous_lambda = current_lambda;
+ current_lambda = lambda;
+
SuiteNode *body = alloc_node<SuiteNode>();
body->parent_function = current_function;
body->parent_block = current_suite;
@@ -3215,6 +3218,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
}
current_function = previous_function;
+ current_lambda = previous_lambda;
in_lambda = previous_in_lambda;
lambda->function = function;
@@ -4283,7 +4287,7 @@ String GDScriptParser::SuiteNode::Local::get_name() const {
case SuiteNode::Local::FOR_VARIABLE:
return "for loop iterator";
case SuiteNode::Local::PATTERN_BIND:
- return "pattern_bind";
+ return "pattern bind";
case SuiteNode::Local::UNDEFINED:
return "<undefined>";
default:
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 652faaebc3..d1ab5f92cc 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -859,9 +859,7 @@ public:
struct IdentifierNode : public ExpressionNode {
StringName name;
-#ifdef DEBUG_ENABLED
SuiteNode *suite = nullptr; // The block in which the identifier is used.
-#endif
enum Source {
UNDEFINED_SOURCE,
@@ -908,6 +906,7 @@ public:
struct LambdaNode : public ExpressionNode {
FunctionNode *function = nullptr;
FunctionNode *parent_function = nullptr;
+ LambdaNode *parent_lambda = nullptr;
Vector<IdentifierNode *> captures;
HashMap<StringName, int> captures_indices;
bool use_self = false;
@@ -1321,6 +1320,7 @@ private:
ClassNode *current_class = nullptr;
FunctionNode *current_function = nullptr;
+ LambdaNode *current_lambda = nullptr;
SuiteNode *current_suite = nullptr;
CompletionContext completion_context;
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_call_arg.gd b/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_call_arg.gd
new file mode 100644
index 0000000000..4b72fb9daa
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_call_arg.gd
@@ -0,0 +1,5 @@
+var f = (func (_a): return 0).call(x)
+var x = f
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_call_arg.out b/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_call_arg.out
new file mode 100644
index 0000000000..6bca25b330
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_call_arg.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Could not resolve member "f": Cyclic reference.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_param.gd b/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_param.gd
new file mode 100644
index 0000000000..115e8be50a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_param.gd
@@ -0,0 +1,5 @@
+var f = func (_a = x): return 0
+var x = f
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_param.out b/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_param.out
new file mode 100644
index 0000000000..6bca25b330
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/lambda_cyclic_ref_param.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Could not resolve member "f": Cyclic reference.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_before_declared.gd b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_before_declared.gd
new file mode 100644
index 0000000000..7cdc14685f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_before_declared.gd
@@ -0,0 +1,5 @@
+enum MyEnum {}
+
+func test():
+ var e: E
+ const E = MyEnum
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_before_declared.out b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_before_declared.out
new file mode 100644
index 0000000000..e1d5837f32
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_before_declared.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Local constant "E" is not resolved at this point.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_const.gd b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_const.gd
new file mode 100644
index 0000000000..68cf5efd8b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_const.gd
@@ -0,0 +1,5 @@
+enum MyEnum {}
+
+func test():
+ var E = MyEnum
+ var e: E
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_const.out b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_const.out
new file mode 100644
index 0000000000..b1f4e7a2c8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_const.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Local variable "E" cannot be used as a type.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_type.gd
new file mode 100644
index 0000000000..ac446183cb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_type.gd
@@ -0,0 +1,5 @@
+enum MyEnum {A}
+
+func test():
+ const E = MyEnum.A
+ var e: E
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_type.out b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_type.out
new file mode 100644
index 0000000000..c3c2c8ca2f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Local constant "E" is not a valid type.
diff --git a/modules/gdscript/tests/scripts/analyzer/features/lambda_cyclic_ref_body.gd b/modules/gdscript/tests/scripts/analyzer/features/lambda_cyclic_ref_body.gd
new file mode 100644
index 0000000000..e2f41a652c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/lambda_cyclic_ref_body.gd
@@ -0,0 +1,34 @@
+# GH-70592
+
+var f: Callable = func ():
+ x = 2
+ return 1
+
+var x: int = f.call()
+
+var g: Array[Callable] = [
+ func ():
+ y += 10
+ return 1,
+ func ():
+ y += 20
+ return 2,
+]
+
+var y: int = g[0].call() + g[1].call()
+
+func test():
+ print(x)
+ f.call()
+ print(x)
+
+ print(y)
+ g[0].call()
+ g[1].call()
+ print(y)
+
+ # This prevents memory leak in CI. TODO: Investigate it.
+ # Also you cannot run the `EditorScript` twice without the cleaning. Error:
+ # Condition "!p_keep_state && has_instances" is true. Returning: ERR_ALREADY_IN_USE
+ f = Callable()
+ g.clear()
diff --git a/modules/gdscript/tests/scripts/analyzer/features/lambda_cyclic_ref_body.out b/modules/gdscript/tests/scripts/analyzer/features/lambda_cyclic_ref_body.out
new file mode 100644
index 0000000000..6917fa7c2c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/lambda_cyclic_ref_body.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+1
+2
+3
+33
diff --git a/modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.gd b/modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.gd
new file mode 100644
index 0000000000..90c7f893b1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.gd
@@ -0,0 +1,41 @@
+class InnerClass:
+ enum InnerEnum {A = 2}
+ const INNER_CONST = "INNER_CONST"
+
+enum Enum {A = 1}
+
+const Other = preload("./local_const_as_type.notest.gd")
+
+func test():
+ const IC = InnerClass
+ const IE = IC.InnerEnum
+ const E = Enum
+ # Doesn't work in CI, but works in the editor. Looks like an unrelated bug. TODO: Investigate it.
+ # Error: Invalid call. Nonexistent function 'new' in base 'GDScript'.
+ var a1: IC = null # IC.new()
+ var a2: IE = IE.A
+ var a3: IC.InnerEnum = IE.A
+ var a4: E = E.A
+ print(a1.INNER_CONST)
+ print(a2)
+ print(a3)
+ print(a4)
+
+ const O = Other
+ const OV: Variant = Other # Removes metatype.
+ const OIC = O.InnerClass
+ const OIE = OIC.InnerEnum
+ const OE = O.Enum
+ var b: O = O.new()
+ @warning_ignore("unsafe_method_access")
+ var bv: OV = OV.new()
+ var b1: OIC = OIC.new()
+ var b2: OIE = OIE.A
+ var b3: O.InnerClass.InnerEnum = OIE.A
+ var b4: OE = OE.A
+ print(b.CONST)
+ print(bv.CONST)
+ print(b1.INNER_CONST)
+ print(b2)
+ print(b3)
+ print(b4)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.notest.gd
new file mode 100644
index 0000000000..f16cdc18d8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.notest.gd
@@ -0,0 +1,6 @@
+class InnerClass:
+ enum InnerEnum {A = 20}
+ const INNER_CONST = "OTHER_INNER_CONST"
+
+enum Enum {A = 10}
+const CONST = "OTHER_CONST"
diff --git a/modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.out b/modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.out
new file mode 100644
index 0000000000..b00024de2c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.out
@@ -0,0 +1,11 @@
+GDTEST_OK
+INNER_CONST
+2
+2
+1
+OTHER_CONST
+OTHER_CONST
+OTHER_INNER_CONST
+20
+20
+10