diff options
Diffstat (limited to 'modules/gdscript/gdscript_analyzer.cpp')
-rw-r--r-- | modules/gdscript/gdscript_analyzer.cpp | 143 |
1 files changed, 110 insertions, 33 deletions
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 01118a6b4f..42b02ce3b9 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -898,7 +898,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { } #ifdef DEBUG_ENABLED - Set<uint32_t> previously_ignored = parser->ignored_warning_codes; + HashSet<uint32_t> previously_ignored = parser->ignored_warning_codes; for (uint32_t ignored_warning : member.function->ignored_warnings) { parser->ignored_warning_codes.insert(ignored_warning); } @@ -947,7 +947,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { GDScriptParser::ClassNode::Member member = p_class->members[i]; if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { #ifdef DEBUG_ENABLED - Set<uint32_t> previously_ignored = parser->ignored_warning_codes; + HashSet<uint32_t> previously_ignored = parser->ignored_warning_codes; for (uint32_t ignored_warning : member.function->ignored_warnings) { parser->ignored_warning_codes.insert(ignored_warning); } @@ -1160,8 +1160,16 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * } } } else { - GDScriptParser::DataType return_type = resolve_datatype(p_function->return_type); - p_function->set_datatype(return_type); + if (p_function->return_type != nullptr) { + p_function->set_datatype(resolve_datatype(p_function->return_type)); + } else { + // In case the function is not typed, we can safely assume it's a Variant, so it's okay to mark as "inferred" here. + // It's not "undetected" to not mix up with unknown functions. + GDScriptParser::DataType return_type; + return_type.type_source = GDScriptParser::DataType::INFERRED; + return_type.kind = GDScriptParser::DataType::VARIANT; + p_function->set_datatype(return_type); + } #ifdef TOOLS_ENABLED // Check if the function signature matches the parent. If not it's an error since it breaks polymorphism. @@ -1231,7 +1239,7 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun GDScriptParser::DataType return_type = p_function->body->get_datatype(); - if (p_function->get_datatype().has_no_type() && return_type.is_set()) { + if (!p_function->get_datatype().is_hard_type() && return_type.is_set()) { // Use the suite inferred type if return isn't explicitly set. return_type.type_source = GDScriptParser::DataType::INFERRED; p_function->set_datatype(p_function->body->get_datatype()); @@ -1279,7 +1287,7 @@ void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) { } #ifdef DEBUG_ENABLED - Set<uint32_t> previously_ignored = parser->ignored_warning_codes; + HashSet<uint32_t> previously_ignored = parser->ignored_warning_codes; for (uint32_t ignored_warning : stmt->ignored_warnings) { parser->ignored_warning_codes.insert(ignored_warning); } @@ -1514,10 +1522,22 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant) { GDScriptParser::DataType type; + GDScriptParser::DataType explicit_type; + if (p_constant->datatype_specifier != nullptr) { + explicit_type = resolve_datatype(p_constant->datatype_specifier); + explicit_type.is_meta_type = false; + } + if (p_constant->initializer != nullptr) { reduce_expression(p_constant->initializer); if (p_constant->initializer->type == GDScriptParser::Node::ARRAY) { - const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_constant->initializer)); + GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(p_constant->initializer); + const_fold_array(array); + + // Can only infer typed array if it has elements. + if (array->elements.size() > 0 || (p_constant->datatype_specifier != nullptr && explicit_type.has_container_element_type())) { + update_array_literal_element_type(explicit_type, array); + } } else if (p_constant->initializer->type == GDScriptParser::Node::DICTIONARY) { const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_constant->initializer)); } @@ -1536,8 +1556,6 @@ void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant } if (p_constant->datatype_specifier != nullptr) { - GDScriptParser::DataType explicit_type = resolve_datatype(p_constant->datatype_specifier); - explicit_type.is_meta_type = false; if (!is_type_compatible(explicit_type, type)) { push_error(vformat(R"(Assigned value for constant "%s" has type %s which is not compatible with defined type %s.)", p_constant->identifier->name, type.to_string(), explicit_type.to_string()), p_constant->initializer); #ifdef DEBUG_ENABLED @@ -1639,8 +1657,8 @@ void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_matc p_match_pattern->bind->set_datatype(result); #ifdef DEBUG_ENABLED is_shadowing(p_match_pattern->bind, "pattern bind"); - if (p_match_pattern->bind->usages == 0) { - parser->push_warning(p_match_pattern->bind, GDScriptWarning::UNASSIGNED_VARIABLE, p_match_pattern->bind->name); + if (p_match_pattern->bind->usages == 0 && !String(p_match_pattern->bind->name).begins_with("_")) { + parser->push_warning(p_match_pattern->bind, GDScriptWarning::UNUSED_VARIABLE, p_match_pattern->bind->name); } #endif break; @@ -2057,7 +2075,8 @@ void GDScriptAnalyzer::reduce_await(GDScriptParser::AwaitNode *p_await) { p_await->set_datatype(awaiting_type); #ifdef DEBUG_ENABLED - if (!awaiting_type.is_coroutine && awaiting_type.builtin_type != Variant::SIGNAL) { + awaiting_type = p_await->to_await->get_datatype(); + if (!(awaiting_type.has_no_type() || awaiting_type.is_coroutine || awaiting_type.builtin_type == Variant::SIGNAL)) { parser->push_warning(p_await, GDScriptWarning::REDUNDANT_AWAIT); } #endif @@ -2174,7 +2193,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) { bool all_is_constant = true; - Map<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing. + HashMap<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing. for (int i = 0; i < p_call->arguments.size(); i++) { reduce_expression(p_call->arguments[i]); if (p_call->arguments[i]->type == GDScriptParser::Node::ARRAY) { @@ -2498,12 +2517,17 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } 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); + // Get the parent function above any lambda. + GDScriptParser::FunctionNode *parent_function = parser->current_function; + while (parent_function->source_lambda) { + parent_function = parent_function->source_lambda->parent_function; + } + push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parent_function->identifier->name), p_call); } else if (!is_self && base_type.is_meta_type && !is_static) { base_type.is_meta_type = false; // For `to_string()`. push_error(vformat(R"*(Cannot call non-static function "%s()" on the class "%s" directly. Make an instance instead.)*", p_call->function_name, base_type.to_string()), p_call); - } 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); + } else if (is_self && !is_static) { + mark_lambda_use_self(); } call_type = return_type; @@ -2636,10 +2660,10 @@ void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) if (!ClassDB::is_parent_class(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); } + mark_lambda_use_self(); + p_get_node->set_datatype(result); } @@ -2854,29 +2878,34 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod MethodBind *getter = ClassDB::get_method(native, getter_name); if (getter != nullptr) { p_identifier->set_datatype(type_from_property(getter->get_return_info())); + p_identifier->source = GDScriptParser::IdentifierNode::INHERITED_VARIABLE; } return; } if (ClassDB::get_method_info(native, name, &method_info)) { // Method is callable. p_identifier->set_datatype(make_callable_type(method_info)); + p_identifier->source = GDScriptParser::IdentifierNode::INHERITED_VARIABLE; return; } if (ClassDB::get_signal(native, name, &method_info)) { // Signal is a type too. p_identifier->set_datatype(make_signal_type(method_info)); + p_identifier->source = GDScriptParser::IdentifierNode::INHERITED_VARIABLE; return; } if (ClassDB::has_enum(native, name)) { p_identifier->set_datatype(make_native_enum_type(native, name)); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; return; } bool valid = false; - int int_constant = ClassDB::get_integer_constant(native, name, &valid); + int64_t int_constant = ClassDB::get_integer_constant(native, name, &valid); if (valid) { p_identifier->is_constant = true; p_identifier->reduced_value = int_constant; p_identifier->set_datatype(type_from_variant(int_constant, p_identifier)); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; return; } } @@ -2927,7 +2956,11 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident p_identifier->reduced_value = p_identifier->constant_source->initializer->reduced_value; found_source = true; break; + case GDScriptParser::IdentifierNode::INHERITED_VARIABLE: + mark_lambda_use_self(); + break; case GDScriptParser::IdentifierNode::MEMBER_VARIABLE: + mark_lambda_use_self(); p_identifier->variable_source->usages++; [[fallthrough]]; case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: @@ -2958,18 +2991,37 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident } 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; + if ((p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE) && parser->current_function && parser->current_function->is_static) { + // Get the parent function above any lambda. + GDScriptParser::FunctionNode *parent_function = parser->current_function; + while (parent_function->source_lambda) { + parent_function = parent_function->source_lambda->parent_function; + } + push_error(vformat(R"*(Cannot access instance variable "%s" from the static function "%s()".)*", p_identifier->name, parent_function->identifier->name), p_identifier); } - 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; + if (!lambda_stack.is_empty()) { + // If the identifier is a member variable (including the native class properties), we consider the lambda to be using `self`, so we keep a reference to the current instance. + if (p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE) { + mark_lambda_use_self(); + return; // No need to capture. + } + // If the identifier is local, check if it's any kind of capture by comparing their source function. + // Only capture locals and enum values. Constants are still accessible from the lambda using the script reference. If not, this method is done. + if (p_identifier->source == GDScriptParser::IdentifierNode::UNDEFINED_SOURCE || p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_CONSTANT) { + return; + } + + GDScriptParser::FunctionNode *function_test = lambda_stack.back()->get()->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)) { + 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; } @@ -3149,6 +3201,7 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) { p_self->is_constant = false; p_self->set_datatype(type_from_metatype(parser->current_class->get_datatype())); + mark_lambda_use_self(); } void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscript) { @@ -3159,6 +3212,12 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base), true); } else { reduce_expression(p_subscript->base); + + if (p_subscript->base->type == GDScriptParser::Node::ARRAY) { + const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_subscript->base)); + } else if (p_subscript->base->type == GDScriptParser::Node::DICTIONARY) { + const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_subscript->base)); + } } GDScriptParser::DataType result_type; @@ -3476,6 +3535,13 @@ void GDScriptAnalyzer::const_fold_array(GDScriptParser::ArrayNode *p_array) { for (int i = 0; i < p_array->elements.size(); i++) { GDScriptParser::ExpressionNode *element = p_array->elements[i]; + + if (element->type == GDScriptParser::Node::ARRAY) { + const_fold_array(static_cast<GDScriptParser::ArrayNode *>(element)); + } else if (element->type == GDScriptParser::Node::DICTIONARY) { + const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(element)); + } + all_is_constant = all_is_constant && element->is_constant; if (!all_is_constant) { return; @@ -3496,6 +3562,13 @@ void GDScriptAnalyzer::const_fold_dictionary(GDScriptParser::DictionaryNode *p_d for (int i = 0; i < p_dictionary->elements.size(); i++) { const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; + + if (element.value->type == GDScriptParser::Node::ARRAY) { + const_fold_array(static_cast<GDScriptParser::ArrayNode *>(element.value)); + } else if (element.value->type == GDScriptParser::Node::DICTIONARY) { + const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(element.value)); + } + all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant; if (!all_is_constant) { return; @@ -4121,6 +4194,12 @@ void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) { #endif } +void GDScriptAnalyzer::mark_lambda_use_self() { + for (GDScriptParser::LambdaNode *lambda : lambda_stack) { + lambda->use_self = true; + } +} + bool GDScriptAnalyzer::class_exists(const StringName &p_class) const { return ClassDB::class_exists(p_class) && ClassDB::is_class_exposed(p_class); } @@ -4158,13 +4237,11 @@ Error GDScriptAnalyzer::resolve_program() { resolve_class_interface(parser->head); resolve_class_body(parser->head); - List<String> parser_keys; - depended_parsers.get_key_list(&parser_keys); - for (const String &E : parser_keys) { - if (depended_parsers[E].is_null()) { + for (KeyValue<String, Ref<GDScriptParserRef>> &K : depended_parsers) { + if (K.value.is_null()) { return ERR_PARSE_ERROR; } - depended_parsers[E]->raise_status(GDScriptParserRef::FULLY_SOLVED); + K.value->raise_status(GDScriptParserRef::FULLY_SOLVED); } return parser->errors.is_empty() ? OK : ERR_PARSE_ERROR; } |