diff options
Diffstat (limited to 'modules/gdscript/gdscript_analyzer.cpp')
-rw-r--r-- | modules/gdscript/gdscript_analyzer.cpp | 211 |
1 files changed, 196 insertions, 15 deletions
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 561cdbbda4..cabe096579 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -33,6 +33,7 @@ #include "core/class_db.h" #include "core/hash_map.h" #include "core/io/resource_loader.h" +#include "core/os/file_access.h" #include "core/project_settings.h" #include "core/script_language.h" #include "gdscript.h" @@ -181,7 +182,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return ERR_PARSE_ERROR; } - Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", p_class->extends_path), p_class); return err; @@ -207,7 +208,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return ERR_PARSE_ERROR; } - Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); return err; @@ -226,7 +227,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return ERR_PARSE_ERROR; } - Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); return err; @@ -301,6 +302,16 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return ERR_PARSE_ERROR; } + // Check for cyclic inheritance. + const GDScriptParser::ClassNode *base_class = result.class_type; + while (base_class) { + if (base_class->fqcn == p_class->fqcn) { + push_error("Cyclic inheritance.", p_class); + return ERR_PARSE_ERROR; + } + base_class = base_class->base_type.class_type; + } + p_class->base_type = result; class_type.native_type = result.native_type; p_class->set_datatype(class_type); @@ -308,7 +319,10 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, if (p_recursive) { for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) { - resolve_inheritance(p_class->members[i].m_class, true); + Error err = resolve_inheritance(p_class->members[i].m_class, true); + if (err) { + return err; + } } } } @@ -498,7 +512,13 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas if (member.variable->initializer != nullptr) { if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) { - push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer); + // Try reverse test since it can be a masked subtype. + if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true)) { + push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer); + } else { + // TODO: Add warning. + mark_node_unsafe(member.variable->initializer); + } } else if (datatype.builtin_type == Variant::INT && member.variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { #ifdef DEBUG_ENABLED parser->push_warning(member.variable->initializer, GDScriptWarning::NARROWING_CONVERSION); @@ -533,6 +553,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas break; case GDScriptParser::DataType::NATIVE: if (ClassDB::is_parent_class(get_real_class_name(datatype.native_type), "Resource")) { + member.variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; member.variable->export_info.hint_string = get_real_class_name(datatype.native_type); } else { push_error(R"(Export type can only be built-in or a resource.)", member.variable); @@ -596,17 +617,67 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas enum_type.is_meta_type = true; enum_type.is_constant = true; + // Enums can't be nested, so we can safely override this. + current_enum = member.m_enum; + for (int j = 0; j < member.m_enum->values.size(); j++) { - enum_type.enum_values[member.m_enum->values[j].identifier->name] = member.m_enum->values[j].value; + GDScriptParser::EnumNode::Value &element = member.m_enum->values.write[j]; + + if (element.custom_value) { + reduce_expression(element.custom_value); + if (!element.custom_value->is_constant) { + push_error(R"(Enum values must be constant.)", element.custom_value); + } else if (element.custom_value->reduced_value.get_type() != Variant::INT) { + push_error(R"(Enum values must be integers.)", element.custom_value); + } else { + element.value = element.custom_value->reduced_value; + element.resolved = true; + } + } else { + if (element.index > 0) { + element.value = element.parent_enum->values[element.index - 1].value + 1; + } else { + element.value = 0; + } + element.resolved = true; + } + + enum_type.enum_values[element.identifier->name] = element.value; } + current_enum = nullptr; + member.m_enum->set_datatype(enum_type); } break; case GDScriptParser::ClassNode::Member::FUNCTION: resolve_function_signature(member.function); break; - case GDScriptParser::ClassNode::Member::ENUM_VALUE: - break; // Nothing to do, type and value set in parser. + case GDScriptParser::ClassNode::Member::ENUM_VALUE: { + if (member.enum_value.custom_value) { + current_enum = member.enum_value.parent_enum; + reduce_expression(member.enum_value.custom_value); + current_enum = nullptr; + + if (!member.enum_value.custom_value->is_constant) { + push_error(R"(Enum values must be constant.)", member.enum_value.custom_value); + } else if (member.enum_value.custom_value->reduced_value.get_type() != Variant::INT) { + push_error(R"(Enum values must be integers.)", member.enum_value.custom_value); + } else { + member.enum_value.value = member.enum_value.custom_value->reduced_value; + member.enum_value.resolved = true; + } + } else { + if (member.enum_value.index > 0) { + member.enum_value.value = member.enum_value.parent_enum->values[member.enum_value.index - 1].value + 1; + } else { + member.enum_value.value = 0; + } + member.enum_value.resolved = true; + } + // Also update the original references. + member.enum_value.parent_enum->values.write[member.enum_value.index] = member.enum_value; + p_class->members.write[i].enum_value = member.enum_value; + } break; case GDScriptParser::ClassNode::Member::CLASS: break; // Done later. case GDScriptParser::ClassNode::Member::UNDEFINED: @@ -994,7 +1065,13 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable if (p_variable->initializer != nullptr) { if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true)) { - push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer); + // Try reverse test since it can be a masked subtype. + if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true)) { + push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer); + } else { + // TODO: Add warning. + mark_node_unsafe(p_variable->initializer); + } #ifdef DEBUG_ENABLED } else if (type.builtin_type == Variant::INT && p_variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { parser->push_warning(p_variable->initializer, GDScriptWarning::NARROWING_CONVERSION); @@ -1385,9 +1462,16 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig compatible = is_type_compatible(p_assignment->assignee->get_datatype(), op_type, true); if (!compatible) { if (p_assignment->assignee->get_datatype().is_hard_type()) { - push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", p_assignment->assigned_value->get_datatype().to_string(), p_assignment->assignee->get_datatype().to_string()), p_assignment->assigned_value); + // Try reverse test since it can be a masked subtype. + if (!is_type_compatible(op_type, p_assignment->assignee->get_datatype(), true)) { + push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", p_assignment->assigned_value->get_datatype().to_string(), p_assignment->assignee->get_datatype().to_string()), p_assignment->assigned_value); + } else { + // TODO: Add warning. + mark_node_unsafe(p_assignment); + } } else { // TODO: Warning in this case. + mark_node_unsafe(p_assignment); } } } else { @@ -1634,6 +1718,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa } signature += p_call->arguments[i]->get_datatype().to_string(); } + signature += ")"; push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call->callee); } break; case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: @@ -1684,7 +1769,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa for (int i = 0; i < p_call->arguments.size(); i++) { GDScriptParser::DataType par_type = type_from_property(info.arguments[i]); - if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype())) { + if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype(), true)) { types_match = false; break; #ifdef DEBUG_ENABLED @@ -1711,6 +1796,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa } signature += p_call->arguments[i]->get_datatype().to_string(); } + signature += ")"; push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call); } } @@ -1889,6 +1975,8 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) { bool all_is_constant = true; + HashMap<Variant, GDScriptParser::ExpressionNode *, VariantHasher, VariantComparator> elements; + for (int i = 0; i < p_dictionary->elements.size(); i++) { const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; if (p_dictionary->style == GDScriptParser::DictionaryNode::PYTHON_DICT) { @@ -1896,6 +1984,14 @@ void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dicti } reduce_expression(element.value); all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant; + + if (element.key->is_constant) { + if (elements.has(element.key->reduced_value)) { + push_error(vformat(R"(Key "%s" was already used in this dictionary (at line %d).)", element.key->reduced_value, elements[element.key->reduced_value]->start_line), element.key); + } else { + elements[element.key->reduced_value] = element.value; + } + } } if (all_is_constant) { @@ -2104,6 +2200,33 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin) { // TODO: This is opportunity to further infer types. + + // Check if we are inside and enum. This allows enum values to access other elements of the same enum. + if (current_enum) { + for (int i = 0; i < current_enum->values.size(); i++) { + const GDScriptParser::EnumNode::Value &element = current_enum->values[i]; + if (element.identifier->name == p_identifier->name) { + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM_VALUE : GDScriptParser::DataType::BUILTIN; + type.builtin_type = Variant::INT; + type.is_constant = true; + if (element.parent_enum->identifier) { + type.enum_type = element.parent_enum->identifier->name; + } + p_identifier->set_datatype(type); + + if (element.resolved) { + p_identifier->is_constant = true; + p_identifier->reduced_value = element.value; + } else { + push_error(R"(Cannot use another enum element before it was declared.)", p_identifier); + } + return; // Found anyway. + } + } + } + // Check if identifier is local. // If that's the case, the declaration already was solved before. switch (p_identifier->source) { @@ -2203,6 +2326,37 @@ void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) { } void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { + if (!p_preload->path) { + return; + } + + reduce_expression(p_preload->path); + + if (!p_preload->path->is_constant) { + push_error("Preloaded path must be a constant string.", p_preload->path); + return; + } + + if (p_preload->path->reduced_value.get_type() != Variant::STRING) { + push_error("Preloaded path must be a constant string.", p_preload->path); + } else { + p_preload->resolved_path = p_preload->path->reduced_value; + // TODO: Save this as script dependency. + if (p_preload->resolved_path.is_rel_path()) { + p_preload->resolved_path = parser->script_path.get_base_dir().plus_file(p_preload->resolved_path); + } + p_preload->resolved_path = p_preload->resolved_path.simplify_path(); + if (!FileAccess::exists(p_preload->resolved_path)) { + push_error(vformat(R"(Preload file "%s" does not exist.)", p_preload->resolved_path), p_preload->path); + } else { + // TODO: Don't load if validating: use completion cache. + p_preload->resource = ResourceLoader::load(p_preload->resolved_path); + if (p_preload->resource.is_null()) { + push_error(vformat(R"(Could not p_preload resource file "%s".)", p_preload->resolved_path), p_preload->path); + } + } + } + p_preload->is_constant = true; p_preload->reduced_value = p_preload->resource; p_preload->set_datatype(type_from_variant(p_preload->reduced_value)); @@ -2378,7 +2532,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri // Check resulting type if possible. result_type.builtin_type = Variant::NIL; result_type.kind = GDScriptParser::DataType::BUILTIN; - result_type.type_source = GDScriptParser::DataType::INFERRED; + result_type.type_source = base_type.is_hard_type() ? GDScriptParser::DataType::ANNOTATED_INFERRED : GDScriptParser::DataType::INFERRED; switch (base_type.builtin_type) { // Can't index at all. @@ -2509,6 +2663,12 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) GDScriptParser::DataType result; + if (p_unary_op->operand == nullptr) { + result.kind = GDScriptParser::DataType::VARIANT; + p_unary_op->set_datatype(result); + return; + } + if (p_unary_op->operand->is_constant) { p_unary_op->is_constant = true; p_unary_op->reduced_value = Variant::evaluate(p_unary_op->variant_op, p_unary_op->operand->reduced_value, Variant()); @@ -2555,9 +2715,27 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va Ref<GDScript> gds = scr; if (gds.is_valid()) { result.kind = GDScriptParser::DataType::CLASS; - Ref<GDScriptParserRef> ref = get_parser_for(gds->get_path()); + // This might be an inner class, so we want to get the parser for the root. + // But still get the inner class from that tree. + GDScript *current = gds.ptr(); + List<StringName> class_chain; + while (current->_owner) { + // Push to front so it's in reverse. + class_chain.push_front(current->name); + current = current->_owner; + } + + Ref<GDScriptParserRef> ref = get_parser_for(current->path); ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); - result.class_type = ref->get_parser()->head; + + GDScriptParser::ClassNode *found = ref->get_parser()->head; + + // It should be okay to assume this exists, since we have a complete script already. + for (const List<StringName>::Element *E = class_chain.front(); E; E = E->next()) { + found = found->get_member(E->get()).m_class; + } + + result.class_type = found; result.script_path = ref->get_parser()->script_path; } else { result.kind = GDScriptParser::DataType::SCRIPT; @@ -2883,7 +3061,7 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator } // Avoid error in formatting operator (%) where it doesn't find a placeholder. - if (a_type == Variant::STRING) { + if (a_type == Variant::STRING && b_type != Variant::ARRAY) { a = String("%s"); } @@ -3089,6 +3267,9 @@ Error GDScriptAnalyzer::resolve_program() { List<String> parser_keys; depended_parsers.get_key_list(&parser_keys); for (const List<String>::Element *E = parser_keys.front(); E != nullptr; E = E->next()) { + if (depended_parsers[E->get()].is_null()) { + return ERR_PARSE_ERROR; + } depended_parsers[E->get()]->raise_status(GDScriptParserRef::FULLY_SOLVED); } depended_parsers.clear(); |