diff options
Diffstat (limited to 'modules/gdscript')
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 |
