diff options
author | George Marques <george@gmarqu.es> | 2020-06-10 19:53:25 -0300 |
---|---|---|
committer | George Marques <george@gmarqu.es> | 2020-07-20 11:38:40 -0300 |
commit | 9a76ab8b6a787edd6e09be4b7833eaf8703cbff7 (patch) | |
tree | 6d741595231cbd7ac8534147c0c46497162d4886 /modules/gdscript | |
parent | 17cd6347ba96d4e64cba475854e842e0a490a465 (diff) |
Add new GDScript type checker
Diffstat (limited to 'modules/gdscript')
-rw-r--r-- | modules/gdscript/gdscript_analyzer.cpp | 2417 | ||||
-rw-r--r-- | modules/gdscript/gdscript_analyzer.h | 65 | ||||
-rw-r--r-- | modules/gdscript/gdscript_compiler.cpp | 10 | ||||
-rw-r--r-- | modules/gdscript/gdscript_editor.cpp | 5 | ||||
-rw-r--r-- | modules/gdscript/gdscript_functions.cpp | 4 | ||||
-rw-r--r-- | modules/gdscript/gdscript_parser.cpp | 2 | ||||
-rw-r--r-- | modules/gdscript/gdscript_parser.h | 5 |
7 files changed, 2404 insertions, 104 deletions
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index efc2ecd9c8..79690e2cf2 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -33,16 +33,102 @@ #include "core/class_db.h" #include "core/hash_map.h" #include "core/io/resource_loader.h" +#include "core/project_settings.h" #include "core/script_language.h" +#include "gdscript.h" -Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) { - GDScriptParser::DataType result; +static GDScriptParser::DataType make_callable_type(const MethodInfo &p_info) { + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = GDScriptParser::DataType::BUILTIN; + type.builtin_type = Variant::CALLABLE; + type.is_constant = true; + type.method_info = p_info; + return type; +} + +static GDScriptParser::DataType make_signal_type(const MethodInfo &p_info) { + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = GDScriptParser::DataType::BUILTIN; + type.builtin_type = Variant::SIGNAL; + type.is_constant = true; + type.method_info = p_info; + return type; +} + +static GDScriptParser::DataType make_native_meta_type(const StringName &p_class_name) { + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = GDScriptParser::DataType::NATIVE; + type.builtin_type = Variant::OBJECT; + type.is_constant = true; + type.native_type = p_class_name; + type.is_meta_type = true; + return type; +} + +static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) { + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = GDScriptParser::DataType::BUILTIN; + type.builtin_type = p_type; + type.is_constant = true; + type.is_meta_type = true; + return type; +} + +// TODO: Move this to a central location (maybe core?). +static HashMap<StringName, StringName> underscore_map; +static const char *underscore_classes[] = { + "ClassDB", + "Directory", + "Engine", + "File", + "Geometry", + "GodotSharp", + "JSON", + "Marshalls", + "Mutex", + "OS", + "ResourceLoader", + "ResourceSaver", + "Semaphore", + "Thread", + "VisualScriptEditor", + nullptr, +}; +static StringName get_real_class_name(const StringName &p_source) { + if (underscore_map.empty()) { + const char **class_name = underscore_classes; + while (*class_name != nullptr) { + underscore_map[*class_name] = String("_") + *class_name; + class_name++; + } + } + if (underscore_map.has(p_source)) { + return underscore_map[p_source]; + } + return p_source; +} +Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) { if (p_class->base_type.is_set()) { // Already resolved return OK; } + GDScriptParser::DataType result; + + // Set datatype for class. + GDScriptParser::DataType class_type; + class_type.is_constant = true; + class_type.is_meta_type = true; + class_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + class_type.kind = GDScriptParser::DataType::CLASS; + class_type.class_type = p_class; + p_class->set_datatype(class_type); + if (!p_class->extends_used) { result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; result.kind = GDScriptParser::DataType::NATIVE; @@ -52,55 +138,115 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, GDScriptParser::DataType base; + int extends_index = 0; + if (!p_class->extends_path.empty()) { - base.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - base.kind = GDScriptParser::DataType::CLASS; - // TODO: Don't load the script here to avoid the issue with cycles. - base.script_type = ResourceLoader::load(p_class->extends_path); - if (base.script_type.is_null() || !base.script_type->is_valid()) { - parser->push_error(vformat(R"(Could not load the parent script at "%s".)", p_class->extends_path)); - } - // TODO: Get this from cache singleton. - base.gdscript_type = nullptr; + Ref<GDScriptParserRef> parser = get_parser_for(p_class->extends_path); + if (parser.is_null()) { + push_error(vformat(R"(Could not resolve super class path "%s".)", p_class->extends_path), p_class); + return ERR_PARSE_ERROR; + } + + Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + if (err != OK) { + push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", p_class->extends_path), p_class); + return err; + } + + base = parser->get_parser()->head->get_datatype(); } else { - const StringName &name = p_class->extends[0]; + const StringName &name = p_class->extends[extends_index++]; base.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; if (ScriptServer::is_global_class(name)) { - base.kind = GDScriptParser::DataType::CLASS; - // TODO: Get this from cache singleton. - base.gdscript_type = nullptr; - // TODO: Try singletons (create main unified source for those). - } else if (p_class->members_indices.has(name)) { - GDScriptParser::ClassNode::Member member = p_class->get_member(name); - - if (member.type == member.CLASS) { - base.kind = GDScriptParser::DataType::CLASS; - base.gdscript_type = member.m_class; - } else if (member.type == member.CONSTANT) { - // FIXME: This could also be a native type or preloaded GDScript. - base.kind = GDScriptParser::DataType::CLASS; - base.gdscript_type = nullptr; + String base_path = ScriptServer::get_global_class_path(name); + + Ref<GDScriptParserRef> parser = get_parser_for(base_path); + if (parser.is_null()) { + push_error(vformat(R"(Could not resolve super class "%s".)", name), p_class); + return ERR_PARSE_ERROR; + } + + Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + if (err != OK) { + push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); + return err; + } + } else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) { + const ProjectSettings::AutoloadInfo &info = ProjectSettings::get_singleton()->get_autoload(name); + if (info.path.get_extension().to_lower() != ".gd") { + push_error(vformat(R"(Singleton %s is not a GDScript.)", info.name), p_class); + return ERR_PARSE_ERROR; + } + + Ref<GDScriptParserRef> parser = get_parser_for(info.path); + if (parser.is_null()) { + push_error(vformat(R"(Could not parse singleton from "%s".)", info.path), p_class); + return ERR_PARSE_ERROR; + } + + Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + if (err != OK) { + push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); + return err; } + } else if (class_exists(name) && ClassDB::can_instance(get_real_class_name(name))) { + base.kind = GDScriptParser::DataType::NATIVE; + base.native_type = name; } else { - if (ClassDB::class_exists(name)) { - base.kind = GDScriptParser::DataType::NATIVE; - base.native_type = name; + // Look for other classes in script. + const GDScriptParser::ClassNode *look_class = p_class; + bool found = false; + while (look_class != nullptr) { + if (p_class->members_indices.has(name) && p_class->get_member(name).type == GDScriptParser::ClassNode::Member::CLASS) { + GDScriptParser::ClassNode::Member member = p_class->get_member(name); + base.kind = GDScriptParser::DataType::CLASS; + base.class_type = member.m_class; + found = true; + break; + } + look_class = look_class->outer; + } + + if (!found) { + push_error(vformat(R"(Could not find base class "%s".)", name), p_class); + return ERR_PARSE_ERROR; } } } - // TODO: Extends with attributes (A.B.C). + for (int index = extends_index; index < p_class->extends.size(); index++) { + if (base.kind != GDScriptParser::DataType::CLASS) { + push_error(R"(Super type "%s" is not a GDScript. Cannot get nested types.)", p_class); + return ERR_PARSE_ERROR; + } + + // TODO: Extends could use identifier nodes. That way errors can be pointed out properly and it can be used here. + GDScriptParser::IdentifierNode *id = parser->alloc_node<GDScriptParser::IdentifierNode>(); + id->name = p_class->extends[index]; + + reduce_identifier_from_base(id, &base); + + GDScriptParser::DataType id_type = id->get_datatype(); + if (!id_type.is_set()) { + push_error(vformat(R"(Could not find type "%s" under base "%s".)", id->name, base.to_string()), p_class); + } + + base = id_type; + } + result = base; } if (!result.is_set()) { // TODO: More specific error messages. - parser->push_error(vformat(R"(Could not resolve inheritance for class "%s".)", p_class->identifier == nullptr ? "<main>" : p_class->identifier->name), p_class); + push_error(vformat(R"(Could not resolve inheritance for class "%s".)", p_class->identifier == nullptr ? "<main>" : p_class->identifier->name), p_class); return ERR_PARSE_ERROR; } - p_class->set_datatype(result); + p_class->base_type = result; + class_type.native_type = result.native_type; + p_class->set_datatype(class_type); if (p_recursive) { for (int i = 0; i < p_class->members.size(); i++) { @@ -113,83 +259,57 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return OK; } -Error GDScriptAnalyzer::resolve_inheritance() { - return resolve_inheritance(parser->head); -} - -// TODO: Move this to a central location (maybe core?). -static HashMap<StringName, StringName> underscore_map; -static const char *underscore_classes[] = { - "ClassDB", - "Directory", - "Engine", - "File", - "Geometry", - "GodotSharp", - "JSON", - "Marshalls", - "Mutex", - "OS", - "ResourceLoader", - "ResourceSaver", - "Semaphore", - "Thread", - "VisualScriptEditor", - nullptr, -}; -static StringName get_real_class_name(const StringName &p_source) { - if (underscore_map.empty()) { - const char **class_name = underscore_classes; - while (*class_name != nullptr) { - underscore_map[*class_name] = String("_") + *class_name; - class_name++; - } - } - if (underscore_map.has(p_source)) { - return underscore_map[p_source]; - } - return p_source; -} - GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(const GDScriptParser::TypeNode *p_type) { GDScriptParser::DataType result; if (p_type == nullptr) { + result.kind = GDScriptParser::DataType::VARIANT; return result; } result.type_source = result.ANNOTATED_EXPLICIT; - if (p_type->type_base == nullptr) { + result.builtin_type = Variant::OBJECT; + + if (p_type->type_chain.empty()) { // void. result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = Variant::NIL; return result; } - StringName first = p_type->type_base->name; + StringName first = p_type->type_chain[0]->name; if (GDScriptParser::get_builtin_type(first) != Variant::NIL) { // Built-in types. - // FIXME: I'm probably using this wrong here (well, I'm not really using it). Specifier *includes* the base the type. - if (p_type->type_specifier != nullptr) { - parser->push_error(R"(Built-in types don't contain subtypes.)", p_type->type_specifier); + if (p_type->type_chain.size() > 1) { + push_error(R"(Built-in types don't contain nested types.)", p_type->type_chain[1]); return GDScriptParser::DataType(); } result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = GDScriptParser::get_builtin_type(first); - } else if (ClassDB::class_exists(get_real_class_name(first))) { + } else if (class_exists(first)) { // Native engine classes. - if (p_type->type_specifier != nullptr) { - parser->push_error(R"(Engine classes don't contain subtypes.)", p_type->type_specifier); + if (p_type->type_chain.size() > 1) { + push_error(R"(Engine classes don't contain nested types.)", p_type->type_chain[1]); return GDScriptParser::DataType(); } result.kind = GDScriptParser::DataType::NATIVE; result.native_type = first; } else if (ScriptServer::is_global_class(first)) { - // Global class_named classes. - // TODO: Global classes and singletons. - parser->push_error("GDScript analyzer: global class type not implemented.", p_type); - ERR_FAIL_V_MSG(GDScriptParser::DataType(), "GDScript analyzer: global class type not implemented."); + Ref<GDScriptParserRef> ref = get_parser_for(ScriptServer::get_global_class_path(first)); + if (ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) { + push_error(vformat(R"(Could not parse global class "%s" from "%s".)", first, ScriptServer::get_global_class_path(first)), p_type); + return GDScriptParser::DataType(); + } + result = ref->get_parser()->head->get_datatype(); + } 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->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) { + push_error(vformat(R"(Could not parse singleton "%s" from "%s".)", first, autoload.path), p_type); + return GDScriptParser::DataType(); + } + result = ref->get_parser()->head->get_datatype(); } else { // Classes in current scope. GDScriptParser::ClassNode *script_class = parser->current_class; @@ -200,32 +320,58 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(const GDScriptParser switch (member.type) { case GDScriptParser::ClassNode::Member::CLASS: result.kind = GDScriptParser::DataType::CLASS; - result.gdscript_type = member.m_class; + result.class_type = member.m_class; found = true; break; + case GDScriptParser::ClassNode::Member::CONSTANT: + if (member.constant->get_datatype().is_meta_type) { + result = member.constant->get_datatype(); + break; + } + [[fallthrough]]; default: // TODO: Get constants as types, disallow others explicitly. - parser->push_error(vformat(R"("%s" is a %s but does not contain a type.)", first, member.get_type_name()), p_type); + push_error(vformat(R"("%s" is a %s but does not contain a type.)", first, member.get_type_name()), p_type); return GDScriptParser::DataType(); } } script_class = script_class->outer; } - parser->push_error(vformat(R"("%s" is not a valid type.)", first), p_type); + push_error(vformat(R"("%s" is not a valid type.)", first), p_type); return GDScriptParser::DataType(); } - // TODO: Allow subtypes. - if (p_type->type_specifier != nullptr) { - parser->push_error(R"(Subtypes are not yet supported.)", p_type->type_specifier); - return GDScriptParser::DataType(); + if (p_type->type_chain.size() > 1) { + // TODO: Allow enums too. + if (result.kind != GDScriptParser::DataType::CLASS) { + push_error(R"(Only GDScript classes can have subtypes.)", p_type->type_chain[1]); + return GDScriptParser::DataType(); + } else { + for (int i = 1; i < p_type->type_chain.size(); i++) { + GDScriptParser::DataType base = result; + reduce_identifier_from_base(p_type->type_chain[i], &base); + result = p_type->type_chain[i]->get_datatype(); + if (!result.is_set()) { + push_error(vformat(R"(Could not find type "%s" under base "%s".)", p_type->type_chain[i]->name, base.to_string()), p_type->type_chain[1]); + return GDScriptParser::DataType(); + } else if (!result.is_meta_type) { + push_error(vformat(R"(Member "%s" under base "%s" is not a valid type.)", p_type->type_chain[i]->name, base.to_string()), p_type->type_chain[1]); + return GDScriptParser::DataType(); + } + } + } } return result; } -Error GDScriptAnalyzer::resolve_datatypes(GDScriptParser::ClassNode *p_class) { +void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_class) { + if (p_class->resolved_interface) { + return; + } + p_class->resolved_interface = true; + GDScriptParser::ClassNode *previous_class = parser->current_class; parser->current_class = p_class; @@ -234,9 +380,29 @@ Error GDScriptAnalyzer::resolve_datatypes(GDScriptParser::ClassNode *p_class) { switch (member.type) { case GDScriptParser::ClassNode::Member::VARIABLE: { - GDScriptParser::DataType datatype = resolve_datatype(member.variable->datatype_specifier); - if (datatype.is_set()) { - member.variable->set_datatype(datatype); + GDScriptParser::DataType datatype; + datatype.kind = GDScriptParser::DataType::VARIANT; + datatype.type_source = GDScriptParser::DataType::UNDETECTED; + + if (member.variable->initializer != nullptr) { + reduce_expression(member.variable->initializer); + datatype = member.variable->initializer->get_datatype(); + } + + if (member.variable->datatype_specifier != nullptr) { + datatype = resolve_datatype(member.variable->datatype_specifier); + } else if (member.variable->infer_datatype) { + if (member.variable->initializer != nullptr) { + push_error(vformat(R"(Cannot infer the type of "%s" variable because there's no default value.)", member.variable->identifier), member.variable->identifier); + } else if (!datatype.is_set() || datatype.has_no_type()) { + push_error(vformat(R"(Cannot infer the type of "%s" variable because the default value doesn't have a set type.)", member.variable->identifier), member.variable->initializer); + } + } + + datatype.is_constant = false; + member.variable->set_datatype(datatype); + if (!datatype.has_no_type()) { + // TODO: Move this out into a routine specific to validate annotations. if (member.variable->export_info.hint == PROPERTY_HINT_TYPE_STRING) { // @export annotation. switch (datatype.kind) { @@ -247,26 +413,2079 @@ Error GDScriptAnalyzer::resolve_datatypes(GDScriptParser::ClassNode *p_class) { if (ClassDB::is_parent_class(get_real_class_name(datatype.native_type), "Resource")) { member.variable->export_info.hint_string = get_real_class_name(datatype.native_type); } else { - parser->push_error(R"(Export type can only be built-in or a resource.)", member.variable); + push_error(R"(Export type can only be built-in or a resource.)", member.variable); } break; default: // TODO: Allow custom user resources. - parser->push_error(R"(Export type can only be built-in or a resource.)", member.variable); + push_error(R"(Export type can only be built-in or a resource.)", member.variable); break; } } } + } break; + case GDScriptParser::ClassNode::Member::CONSTANT: { + reduce_expression(member.constant->initializer); + + if (!member.constant->initializer->is_constant) { + push_error(R"(Initializer for a constant must be a constant expression.)", member.constant->initializer); + } + member.constant->set_datatype(member.constant->initializer->get_datatype()); + } break; + case GDScriptParser::ClassNode::Member::SIGNAL: { + for (int j = 0; j < member.signal->parameters.size(); j++) { + member.signal->parameters[j]->set_datatype(resolve_datatype(member.signal->parameters[j]->datatype_specifier)); + } + // TODO: Make MethodInfo from signal. + GDScriptParser::DataType signal_type; + signal_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + signal_type.kind = GDScriptParser::DataType::BUILTIN; + signal_type.builtin_type = Variant::SIGNAL; + + member.signal->set_datatype(signal_type); + } break; + case GDScriptParser::ClassNode::Member::ENUM: { + GDScriptParser::DataType enum_type; + enum_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + enum_type.kind = GDScriptParser::DataType::BUILTIN; + enum_type.builtin_type = Variant::DICTIONARY; + + 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::CLASS: + break; // Done later. + case GDScriptParser::ClassNode::Member::UNDEFINED: + ERR_PRINT("Trying to resolve undefined member."); break; + } + } + + // Recurse nested classes. + for (int i = 0; i < p_class->members.size(); i++) { + GDScriptParser::ClassNode::Member member = p_class->members[i]; + if (member.type != GDScriptParser::ClassNode::Member::CLASS) { + continue; + } + + resolve_class_interface(member.m_class); + } + + parser->current_class = previous_class; +} + +void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { + if (p_class->resolved_body) { + return; + } + p_class->resolved_body = true; + + GDScriptParser::ClassNode *previous_class = parser->current_class; + parser->current_class = p_class; + + // Do functions now. + for (int i = 0; i < p_class->members.size(); i++) { + GDScriptParser::ClassNode::Member member = p_class->members[i]; + if (member.type != GDScriptParser::ClassNode::Member::FUNCTION) { + continue; + } + + resolve_function_body(member.function); + } + + parser->current_class = previous_class; + + // Recurse nested classes. + for (int i = 0; i < p_class->members.size(); i++) { + GDScriptParser::ClassNode::Member member = p_class->members[i]; + if (member.type != GDScriptParser::ClassNode::Member::CLASS) { + continue; + } + + resolve_class_body(member.m_class); + } +} + +void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) { + ERR_FAIL_COND_MSG(p_node == nullptr, "Trying to resolve type of a null node."); + + switch (p_node->type) { + case GDScriptParser::Node::NONE: + break; // Unreachable. + case GDScriptParser::Node::CLASS: + resolve_class_interface(static_cast<GDScriptParser::ClassNode *>(p_node)); + resolve_class_body(static_cast<GDScriptParser::ClassNode *>(p_node)); + break; + case GDScriptParser::Node::CONSTANT: + resolve_constant(static_cast<GDScriptParser::ConstantNode *>(p_node)); + break; + case GDScriptParser::Node::FOR: + resolve_for(static_cast<GDScriptParser::ForNode *>(p_node)); + break; + case GDScriptParser::Node::FUNCTION: + resolve_function_signature(static_cast<GDScriptParser::FunctionNode *>(p_node)); + resolve_function_body(static_cast<GDScriptParser::FunctionNode *>(p_node)); + break; + case GDScriptParser::Node::IF: + resolve_if(static_cast<GDScriptParser::IfNode *>(p_node)); + break; + case GDScriptParser::Node::SUITE: + resolve_suite(static_cast<GDScriptParser::SuiteNode *>(p_node)); + break; + case GDScriptParser::Node::VARIABLE: + resolve_variable(static_cast<GDScriptParser::VariableNode *>(p_node)); + break; + case GDScriptParser::Node::WHILE: + resolve_while(static_cast<GDScriptParser::WhileNode *>(p_node)); + break; + case GDScriptParser::Node::ANNOTATION: + resolve_annotation(static_cast<GDScriptParser::AnnotationNode *>(p_node)); + break; + case GDScriptParser::Node::ASSERT: + resolve_assert(static_cast<GDScriptParser::AssertNode *>(p_node)); + break; + case GDScriptParser::Node::MATCH: + resolve_match(static_cast<GDScriptParser::MatchNode *>(p_node)); + break; + case GDScriptParser::Node::MATCH_BRANCH: + resolve_match_branch(static_cast<GDScriptParser::MatchBranchNode *>(p_node), nullptr); + break; + case GDScriptParser::Node::PARAMETER: + resolve_pararameter(static_cast<GDScriptParser::ParameterNode *>(p_node)); + break; + case GDScriptParser::Node::PATTERN: + resolve_match_pattern(static_cast<GDScriptParser::PatternNode *>(p_node), nullptr); + break; + case GDScriptParser::Node::RETURN: + resolve_return(static_cast<GDScriptParser::ReturnNode *>(p_node)); + break; + case GDScriptParser::Node::TYPE: + resolve_datatype(static_cast<GDScriptParser::TypeNode *>(p_node)); + break; + // Resolving expression is the same as reducing them. + case GDScriptParser::Node::ARRAY: + case GDScriptParser::Node::ASSIGNMENT: + case GDScriptParser::Node::AWAIT: + case GDScriptParser::Node::BINARY_OPERATOR: + case GDScriptParser::Node::CALL: + case GDScriptParser::Node::CAST: + case GDScriptParser::Node::DICTIONARY: + case GDScriptParser::Node::GET_NODE: + case GDScriptParser::Node::IDENTIFIER: + case GDScriptParser::Node::LITERAL: + case GDScriptParser::Node::PRELOAD: + case GDScriptParser::Node::SELF: + case GDScriptParser::Node::SUBSCRIPT: + case GDScriptParser::Node::TERNARY_OPERATOR: + case GDScriptParser::Node::UNARY_OPERATOR: + reduce_expression(static_cast<GDScriptParser::ExpressionNode *>(p_node)); + break; + case GDScriptParser::Node::BREAK: + case GDScriptParser::Node::BREAKPOINT: + case GDScriptParser::Node::CONTINUE: + case GDScriptParser::Node::ENUM: + case GDScriptParser::Node::PASS: + case GDScriptParser::Node::SIGNAL: + // Nothing to do. + break; + } +} + +void GDScriptAnalyzer::resolve_annotation(GDScriptParser::AnnotationNode *p_annotation) { + // TODO: Add second validation function for annotations, so they can use checked types. +} + +void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *p_function) { + if (p_function->resolved_signature) { + return; + } + p_function->resolved_signature = true; + + GDScriptParser::FunctionNode *previous_function = parser->current_function; + parser->current_function = p_function; + + for (int i = 0; i < p_function->parameters.size(); i++) { + resolve_pararameter(p_function->parameters[i]); + } + + p_function->set_datatype(resolve_datatype(p_function->return_type)); + + parser->current_function = previous_function; +} + +void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_function) { + if (p_function->resolved_body) { + return; + } + p_function->resolved_body = true; + + GDScriptParser::FunctionNode *previous_function = parser->current_function; + parser->current_function = p_function; + + resolve_suite(p_function->body); + + GDScriptParser::DataType return_type = p_function->body->get_datatype(); + + if (p_function->get_datatype().type_source == GDScriptParser::DataType::UNDETECTED && 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()); + } + + parser->current_function = previous_function; +} + +void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement) { + switch (p_statement->type) { + case GDScriptParser::Node::IF: + case GDScriptParser::Node::FOR: + case GDScriptParser::Node::MATCH: + case GDScriptParser::Node::PATTERN: + case GDScriptParser::Node::RETURN: + case GDScriptParser::Node::WHILE: + // Use return or nested suite type as this suite type. + if (p_suite->get_datatype().is_set() && (p_suite->get_datatype() != p_statement->get_datatype())) { + // Mixed types. + // TODO: This could use the common supertype instead. + p_suite->datatype.kind = GDScriptParser::DataType::VARIANT; + p_suite->datatype.type_source = GDScriptParser::DataType::UNDETECTED; + } else { + p_suite->set_datatype(p_statement->get_datatype()); + } + break; + default: + break; + } +} + +void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) { + for (int i = 0; i < p_suite->statements.size(); i++) { + GDScriptParser::Node *stmt = p_suite->statements[i]; + resolve_node(stmt); + decide_suite_type(p_suite, stmt); + } +} + +void GDScriptAnalyzer::resolve_if(GDScriptParser::IfNode *p_if) { + reduce_expression(p_if->condition); + + resolve_suite(p_if->true_block); + p_if->set_datatype(p_if->true_block->get_datatype()); + + if (p_if->false_block != nullptr) { + resolve_suite(p_if->false_block); + decide_suite_type(p_if, p_if->false_block); + } +} + +void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { + bool list_resolved = false; + + // Optimize constant range() call to not allocate an array. + // Use int, Vector2, Vector3 instead, which also can be used as range iterators. + if (p_for->list->type == GDScriptParser::Node::CALL) { + GDScriptParser::CallNode *call = static_cast<GDScriptParser::CallNode *>(p_for->list); + if (call->callee->type == GDScriptParser::Node::IDENTIFIER) { + GDScriptParser::IdentifierNode *callee = static_cast<GDScriptParser::IdentifierNode *>(call->callee); + if (callee->name == "range") { + list_resolved = true; + if (call->arguments.size() < 1) { + push_error(R"*(Invalid call for "range()" function. Expected at least 1 argument, none given.)*", call->callee); + } else if (call->arguments.size() > 3) { + push_error(vformat(R"*(Invalid call for "range()" function. Expected at most 3 arguments, %d given.)*", call->arguments.size()), call->callee); + } else { + // Now we can optimize it. + bool all_is_constant = true; + Vector<Variant> args; + args.resize(call->arguments.size()); + for (int i = 0; i < call->arguments.size(); i++) { + reduce_expression(call->arguments[i]); + + if (!call->arguments[i]->is_constant) { + all_is_constant = false; + } else { + args.write[i] = call->arguments[i]->reduced_value; + } + + GDScriptParser::DataType arg_type = call->arguments[i]->get_datatype(); + if (arg_type.kind != GDScriptParser::DataType::BUILTIN) { + all_is_constant = false; + push_error(vformat(R"*(Invalid argument for "range()" call. Argument %d should be int or float but "%s" was given.)*", i + 1, arg_type.to_string()), call->arguments[i]); + } else if (arg_type.builtin_type != Variant::INT && arg_type.builtin_type != Variant::FLOAT) { + all_is_constant = false; + push_error(vformat(R"*(Invalid argument for "range()" call. Argument %d should be int or float but "%s" was given.)*", i + 1, arg_type.to_string()), call->arguments[i]); + } + } + + Variant reduced; + + if (all_is_constant) { + switch (args.size()) { + case 1: + reduced = args[0]; + break; + case 2: + reduced = Vector2i(args[0], args[1]); + break; + case 3: + reduced = Vector3i(args[0], args[1], args[2]); + break; + } + p_for->list->is_constant = true; + p_for->list->reduced_value = reduced; + } + } + + GDScriptParser::DataType list_type; + list_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + list_type.kind = GDScriptParser::DataType::BUILTIN; + list_type.builtin_type = Variant::ARRAY; + p_for->list->set_datatype(list_type); + } + } + } + + if (!list_resolved) { + resolve_node(p_for->list); + } + + // TODO: If list is a typed array, the variable should be an element. + // Also applicable for constant range() (so variable is int or float). + + resolve_suite(p_for->loop); + p_for->set_datatype(p_for->loop->get_datatype()); +} + +void GDScriptAnalyzer::resolve_while(GDScriptParser::WhileNode *p_while) { + resolve_node(p_while->condition); + + resolve_suite(p_while->loop); + p_while->set_datatype(p_while->loop->get_datatype()); +} + +void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable) { + GDScriptParser::DataType type; + + if (p_variable->initializer != nullptr) { + reduce_expression(p_variable->initializer); + type = p_variable->initializer->get_datatype(); + + if (p_variable->infer_datatype) { + type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + + if (type.has_no_type()) { + push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value does not have a set type.)", p_variable->identifier->name), p_variable->initializer); + } else if (type.is_variant()) { + push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is a variant.)", p_variable->identifier->name), p_variable->initializer); + } + } else { + type.type_source = GDScriptParser::DataType::INFERRED; + } + } + + if (p_variable->datatype_specifier != nullptr) { + type = resolve_datatype(p_variable->datatype_specifier); + } + + type.is_constant = false; + p_variable->set_datatype(type); +} + +void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant) { + GDScriptParser::DataType type; + + reduce_expression(p_constant->initializer); + + if (!p_constant->initializer->is_constant) { + push_error(vformat(R"(Assigned value for constant "%s" isn't a constant expression.)", p_constant->identifier->name), p_constant->initializer); + } + + type = p_constant->initializer->get_datatype(); + + if (p_constant->datatype_specifier != nullptr) { + GDScriptParser::DataType explicit_type = resolve_datatype(p_constant->datatype_specifier); + 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); + } + type = explicit_type; + } + + type.is_constant = true; + p_constant->set_datatype(type); +} + +void GDScriptAnalyzer::resolve_assert(GDScriptParser::AssertNode *p_assert) { + reduce_expression(p_assert->condition); + reduce_literal(p_assert->message); + + p_assert->set_datatype(p_assert->condition->get_datatype()); + + // TODO: Warn when expression is constantly true or false. +} + +void GDScriptAnalyzer::resolve_match(GDScriptParser::MatchNode *p_match) { + reduce_expression(p_match->test); + + for (int i = 0; i < p_match->branches.size(); i++) { + resolve_match_branch(p_match->branches[i], p_match->test); + + decide_suite_type(p_match, p_match->branches[i]); + } +} + +void GDScriptAnalyzer::resolve_match_branch(GDScriptParser::MatchBranchNode *p_match_branch, GDScriptParser::ExpressionNode *p_match_test) { + for (int i = 0; i < p_match_branch->patterns.size(); i++) { + resolve_match_pattern(p_match_branch->patterns[i], p_match_test); + } + + resolve_suite(p_match_branch->block); + + decide_suite_type(p_match_branch, p_match_branch->block); +} + +void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_match_pattern, GDScriptParser::ExpressionNode *p_match_test) { + GDScriptParser::DataType result; + + switch (p_match_pattern->pattern_type) { + case GDScriptParser::PatternNode::PT_LITERAL: + reduce_literal(p_match_pattern->literal); + result = p_match_pattern->literal->get_datatype(); + break; + case GDScriptParser::PatternNode::PT_EXPRESSION: + reduce_expression(p_match_pattern->expression); + if (!p_match_pattern->expression->is_constant) { + push_error(R"(Expression in match pattern must be a constant.)", p_match_pattern->expression); + } + result = p_match_pattern->expression->get_datatype(); + break; + case GDScriptParser::PatternNode::PT_BIND: + if (p_match_test != nullptr) { + result = p_match_test->get_datatype(); + } else { + result.kind = GDScriptParser::DataType::VARIANT; + } + p_match_pattern->bind->set_datatype(result); + break; + case GDScriptParser::PatternNode::PT_ARRAY: + for (int i = 0; i < p_match_pattern->array.size(); i++) { + resolve_match_pattern(p_match_pattern->array[i], nullptr); + decide_suite_type(p_match_pattern, p_match_pattern->array[i]); + } + result = p_match_pattern->get_datatype(); + break; + case GDScriptParser::PatternNode::PT_DICTIONARY: + for (int i = 0; i < p_match_pattern->dictionary.size(); i++) { + reduce_expression(p_match_pattern->dictionary[i].key); + if (!p_match_pattern->dictionary[i].key->is_constant) { + push_error(R"(Expression in dictionary pattern key must be a constant.)", p_match_pattern->expression); + } + + resolve_match_pattern(p_match_pattern->dictionary[i].value_pattern, nullptr); + decide_suite_type(p_match_pattern, p_match_pattern->dictionary[i].value_pattern); + } + result = p_match_pattern->get_datatype(); + break; + case GDScriptParser::PatternNode::PT_WILDCARD: + case GDScriptParser::PatternNode::PT_REST: + result.kind = GDScriptParser::DataType::VARIANT; + break; + } + + p_match_pattern->set_datatype(result); +} + +void GDScriptAnalyzer::resolve_pararameter(GDScriptParser::ParameterNode *p_parameter) { + GDScriptParser::DataType result; + result.kind = GDScriptParser::DataType::VARIANT; + + if (p_parameter->default_value != nullptr) { + reduce_expression(p_parameter->default_value); + if (p_parameter->default_value->is_constant) { + push_error(vformat(R"(Default value for paramater "%s" is not a constant.)", p_parameter->identifier->name), p_parameter->default_value); + } + result = p_parameter->default_value->get_datatype(); + result.type_source = GDScriptParser::DataType::INFERRED; + } + + if (p_parameter->datatype_specifier != nullptr) { + resolve_datatype(p_parameter->datatype_specifier); + + if (p_parameter->default_value != nullptr) { + if (!is_type_compatible(p_parameter->datatype_specifier->get_datatype(), p_parameter->default_value->get_datatype())) { + push_error(vformat(R"(Type of default value for parameter "%s" is not compatible with paremeter type.)", p_parameter->identifier->name), p_parameter->default_value); } + } + + result = p_parameter->datatype_specifier->get_datatype(); + } + + p_parameter->set_datatype(result); +} + +void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) { + GDScriptParser::DataType result; + + if (p_return->return_value != nullptr) { + reduce_expression(p_return->return_value); + result = p_return->return_value->get_datatype(); + } else { + // Return type is null by default. + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + result.kind = GDScriptParser::DataType::BUILTIN; + result.builtin_type = Variant::NIL; + result.is_constant = true; + } + + p_return->set_datatype(result); +} + +void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expression) { + // This one makes some magic happen. + + if (p_expression->reduced) { + // Don't do this more than once. + return; + } + + p_expression->reduced = true; + + switch (p_expression->type) { + case GDScriptParser::Node::ARRAY: + reduce_array(static_cast<GDScriptParser::ArrayNode *>(p_expression)); + break; + case GDScriptParser::Node::ASSIGNMENT: + reduce_assignment(static_cast<GDScriptParser::AssignmentNode *>(p_expression)); + break; + case GDScriptParser::Node::AWAIT: + reduce_await(static_cast<GDScriptParser::AwaitNode *>(p_expression)); + break; + case GDScriptParser::Node::BINARY_OPERATOR: + reduce_binary_op(static_cast<GDScriptParser::BinaryOpNode *>(p_expression)); + break; + case GDScriptParser::Node::CALL: + reduce_call(static_cast<GDScriptParser::CallNode *>(p_expression)); + break; + case GDScriptParser::Node::CAST: + reduce_cast(static_cast<GDScriptParser::CastNode *>(p_expression)); + break; + case GDScriptParser::Node::DICTIONARY: + reduce_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_expression)); + break; + case GDScriptParser::Node::GET_NODE: + reduce_get_node(static_cast<GDScriptParser::GetNodeNode *>(p_expression)); + break; + case GDScriptParser::Node::IDENTIFIER: + reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_expression)); + break; + case GDScriptParser::Node::LITERAL: + reduce_literal(static_cast<GDScriptParser::LiteralNode *>(p_expression)); + break; + case GDScriptParser::Node::PRELOAD: + reduce_preload(static_cast<GDScriptParser::PreloadNode *>(p_expression)); + break; + case GDScriptParser::Node::SELF: + reduce_self(static_cast<GDScriptParser::SelfNode *>(p_expression)); + break; + case GDScriptParser::Node::SUBSCRIPT: + reduce_subscript(static_cast<GDScriptParser::SubscriptNode *>(p_expression)); + break; + case GDScriptParser::Node::TERNARY_OPERATOR: + reduce_ternary_op(static_cast<GDScriptParser::TernaryOpNode *>(p_expression)); + break; + case GDScriptParser::Node::UNARY_OPERATOR: + reduce_unary_op(static_cast<GDScriptParser::UnaryOpNode *>(p_expression)); + break; + // Non-expressions. Here only to make sure new nodes aren't forgotten. + case GDScriptParser::Node::NONE: + case GDScriptParser::Node::ANNOTATION: + case GDScriptParser::Node::ASSERT: + case GDScriptParser::Node::BREAK: + case GDScriptParser::Node::BREAKPOINT: + case GDScriptParser::Node::CLASS: + case GDScriptParser::Node::CONSTANT: + case GDScriptParser::Node::CONTINUE: + case GDScriptParser::Node::ENUM: + case GDScriptParser::Node::FOR: + case GDScriptParser::Node::FUNCTION: + case GDScriptParser::Node::IF: + case GDScriptParser::Node::MATCH: + case GDScriptParser::Node::MATCH_BRANCH: + case GDScriptParser::Node::PARAMETER: + case GDScriptParser::Node::PASS: + case GDScriptParser::Node::PATTERN: + case GDScriptParser::Node::RETURN: + case GDScriptParser::Node::SIGNAL: + case GDScriptParser::Node::SUITE: + case GDScriptParser::Node::TYPE: + case GDScriptParser::Node::VARIABLE: + case GDScriptParser::Node::WHILE: + ERR_FAIL_MSG("Reaching unreachable case"); + } +} + +void GDScriptAnalyzer::reduce_array(GDScriptParser::ArrayNode *p_array) { + bool all_is_constant = true; + + for (int i = 0; i < p_array->elements.size(); i++) { + GDScriptParser::ExpressionNode *element = p_array->elements[i]; + reduce_expression(element); + all_is_constant = all_is_constant && element->is_constant; + } + + if (all_is_constant) { + Array array; + array.resize(p_array->elements.size()); + for (int i = 0; i < p_array->elements.size(); i++) { + array[i] = p_array->elements[i]->reduced_value; + } + p_array->is_constant = true; + p_array->reduced_value = array; + } + + // It's array in any case. + GDScriptParser::DataType arr_type; + arr_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + arr_type.kind = GDScriptParser::DataType::BUILTIN; + arr_type.builtin_type = Variant::ARRAY; + arr_type.is_constant = true; + + p_array->set_datatype(arr_type); +} + +void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) { + reduce_expression(p_assignment->assignee); + reduce_expression(p_assignment->assigned_value); + + if (p_assignment->assignee->get_datatype().is_constant) { + push_error("Cannot assign a new value to a constant.", p_assignment->assignee); + } + + if (!is_type_compatible(p_assignment->assignee->get_datatype(), p_assignment->assigned_value->get_datatype())) { + 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); + } else { + // TODO: Warning in this case. + } + } + + if (p_assignment->assignee->get_datatype().has_no_type() || p_assignment->assigned_value->get_datatype().is_variant()) { + mark_node_unsafe(p_assignment); + } + + if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) { + // Change source type so it's not wrongly detected later. + GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(p_assignment->assignee); + + switch (identifier->source) { + case GDScriptParser::IdentifierNode::MEMBER_VARIABLE: { + GDScriptParser::DataType id_type = identifier->variable_source->get_datatype(); + if (!id_type.is_hard_type()) { + id_type.kind = GDScriptParser::DataType::VARIANT; + id_type.type_source = GDScriptParser::DataType::UNDETECTED; + identifier->variable_source->set_datatype(id_type); + } + } break; + case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: { + GDScriptParser::DataType id_type = identifier->variable_source->get_datatype(); + if (!id_type.is_hard_type()) { + id_type = p_assignment->assigned_value->get_datatype(); + id_type.type_source = GDScriptParser::DataType::INFERRED; + id_type.is_constant = false; + identifier->variable_source->set_datatype(id_type); + } + } break; + case GDScriptParser::IdentifierNode::LOCAL_ITERATOR: { + GDScriptParser::DataType id_type = identifier->bind_source->get_datatype(); + if (!id_type.is_hard_type()) { + id_type = p_assignment->assigned_value->get_datatype(); + id_type.type_source = GDScriptParser::DataType::INFERRED; + id_type.is_constant = false; + identifier->variable_source->set_datatype(id_type); + } + } break; default: - // TODO + // Nothing to do. break; } } - parser->current_class = previous_class; +} + +void GDScriptAnalyzer::reduce_await(GDScriptParser::AwaitNode *p_await) { + reduce_expression(p_await->to_await); + + p_await->is_constant = p_await->to_await->is_constant; + p_await->reduced_value = p_await->to_await->reduced_value; + + GDScriptParser::DataType awaiting_type = p_await->to_await->get_datatype(); + + p_await->set_datatype(awaiting_type); + + // TODO: Add warning if await is redundant. +} + +void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_op) { + reduce_expression(p_binary_op->left_operand); + reduce_expression(p_binary_op->right_operand); + + // TODO: Right operand must be a valid type with the `is` operator. Need to check here. + + if (p_binary_op->left_operand->is_constant && p_binary_op->right_operand->is_constant) { + p_binary_op->is_constant = true; + if (p_binary_op->variant_op < Variant::OP_MAX) { + p_binary_op->reduced_value = Variant::evaluate(p_binary_op->variant_op, p_binary_op->left_operand->reduced_value, p_binary_op->right_operand->reduced_value); + } else { + if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) { + GDScriptParser::DataType test_type = p_binary_op->right_operand->get_datatype(); + test_type.is_meta_type = false; + + if (!is_type_compatible(test_type, p_binary_op->left_operand->get_datatype(), false)) { + push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)"), p_binary_op->left_operand); + p_binary_op->reduced_value = false; + } else { + p_binary_op->reduced_value = true; + } + } else { + ERR_PRINT("Parser bug: unknown binary operation."); + } + } + p_binary_op->set_datatype(type_from_variant(p_binary_op->reduced_value)); + return; + } + + GDScriptParser::DataType result; + + if (p_binary_op->left_operand->get_datatype().is_variant() || p_binary_op->right_operand->get_datatype().is_variant()) { + // Cannot infer type because one operand can be anything. + result.kind = GDScriptParser::DataType::VARIANT; + mark_node_unsafe(p_binary_op); + } else { + if (p_binary_op->variant_op < Variant::OP_MAX) { + bool valid = false; + result = get_operation_type(p_binary_op->variant_op, p_binary_op->left_operand->get_datatype(), p_binary_op->right_operand->get_datatype(), valid); + + if (!valid) { + push_error(vformat(R"(Invalid operands "%s" and "%s" for "%s" operator.)", p_binary_op->left_operand->get_datatype().to_string(), p_binary_op->right_operand->get_datatype().to_string(), Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op); + } + } else { + if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) { + GDScriptParser::DataType test_type = p_binary_op->right_operand->get_datatype(); + test_type.is_meta_type = false; + + if (!is_type_compatible(test_type, p_binary_op->left_operand->get_datatype(), false)) { + if (p_binary_op->left_operand->get_datatype().is_hard_type()) { + push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", p_binary_op->left_operand->get_datatype().to_string(), test_type.to_string()), p_binary_op->left_operand); + } else { + // TODO: Warning. + } + } + + // "is" operator is always a boolean anyway. + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + result.kind = GDScriptParser::DataType::BUILTIN; + result.builtin_type = Variant::BOOL; + } else { + ERR_PRINT("Parser bug: unknown binary operation."); + } + } + } + + p_binary_op->set_datatype(result); +} + +void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call) { + bool all_is_constant = true; + for (int i = 0; i < p_call->arguments.size(); i++) { + reduce_expression(p_call->arguments[i]); + all_is_constant = all_is_constant && p_call->arguments[i]->is_constant; + } + + GDScriptParser::DataType call_type; + + if (!p_call->is_super && p_call->callee->type == GDScriptParser::Node::IDENTIFIER) { + // Call to name directly. + StringName function_name = p_call->function_name; + Variant::Type builtin_type = GDScriptParser::get_builtin_type(function_name); + GDScriptFunctions::Function builtin_function = GDScriptParser::get_builtin_function(function_name); + + if (builtin_type < Variant::VARIANT_MAX) { + // Is a builtin constructor. + call_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + call_type.kind = GDScriptParser::DataType::BUILTIN; + call_type.builtin_type = builtin_type; + + if (builtin_type == Variant::OBJECT) { + call_type.kind = GDScriptParser::DataType::NATIVE; + call_type.native_type = function_name; // "Object". + } + + if (all_is_constant) { + // Construct here. + Vector<const Variant *> args; + for (int i = 0; i < p_call->arguments.size(); i++) { + args.push_back(&(p_call->arguments[i]->reduced_value)); + } + + Callable::CallError err; + Variant value = Variant::construct(builtin_type, (const Variant **)args.ptr(), args.size(), err); + + switch (err.error) { + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: + push_error(vformat(R"(Invalid argument for %s constructor: argument %d should be %s but is %s.)", Variant::get_type_name(builtin_type), err.argument + 1, + Variant::get_type_name(Variant::Type(err.expected)), p_call->arguments[err.argument]->get_datatype().to_string()), + p_call->arguments[err.argument]); + break; + case Callable::CallError::CALL_ERROR_INVALID_METHOD: { + String signature = Variant::get_type_name(builtin_type) + "("; + for (int i = 0; i < p_call->arguments.size(); i++) { + if (i > 0) { + signature += ", "; + } + signature += p_call->arguments[i]->get_datatype().to_string(); + } + 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: + push_error(vformat(R"(Too many arguments for %s constructor. Received %d but expected %d.)", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); + break; + case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: + push_error(vformat(R"(Too few arguments for %s constructor. Received %d but expected %d.)", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); + break; + case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: + break; // Can't happen in a builtin constructor. + case Callable::CallError::CALL_OK: + p_call->is_constant = true; + p_call->reduced_value = value; + break; + } + } else { + // TODO: Check constructors without constants. + + // If there's one argument, try to use copy constructor (those aren't explicitly defined). + if (p_call->arguments.size() == 1) { + GDScriptParser::DataType arg_type = p_call->arguments[0]->get_datatype(); + if (arg_type.is_variant()) { + mark_node_unsafe(p_call->arguments[0]); + } else { + if (arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == builtin_type) { + // Okay. + p_call->set_datatype(call_type); + return; + } + } + } + List<MethodInfo> constructors; + Variant::get_constructor_list(builtin_type, &constructors); + bool match = false; + + for (const List<MethodInfo>::Element *E = constructors.front(); E != nullptr; E = E->next()) { + const MethodInfo &info = E->get(); + + if (p_call->arguments.size() < info.arguments.size() - info.default_arguments.size()) { + continue; + } + if (p_call->arguments.size() > info.arguments.size()) { + continue; + } + + bool types_match = true; + + 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())) { + types_match = false; + break; + } else { + // TODO: Check narrowing conversion. + } + } + + if (types_match) { + match = true; + call_type = type_from_property(info.return_val); + break; + } + } + + if (!match) { + String signature = Variant::get_type_name(builtin_type) + "("; + for (int i = 0; i < p_call->arguments.size(); i++) { + if (i > 0) { + signature += ", "; + } + signature += p_call->arguments[i]->get_datatype().to_string(); + } + push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call); + } + } + p_call->set_datatype(call_type); + return; + } else if (builtin_function < GDScriptFunctions::FUNC_MAX) { + MethodInfo function_info = GDScriptFunctions::get_info(builtin_function); + + if (all_is_constant && GDScriptFunctions::is_deterministic(builtin_function)) { + // Can call on compilation. + Vector<const Variant *> args; + for (int i = 0; i < p_call->arguments.size(); i++) { + args.push_back(&(p_call->arguments[i]->reduced_value)); + } + + Variant value; + Callable::CallError err; + GDScriptFunctions::call(builtin_function, (const Variant **)args.ptr(), args.size(), value, err); + + switch (err.error) { + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { + PropertyInfo wrong_arg = function_info.arguments[err.argument]; + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", GDScriptFunctions::get_func_name(builtin_function), err.argument + 1, + type_from_property(wrong_arg).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()), + p_call->arguments[err.argument]); + } break; + case Callable::CallError::CALL_ERROR_INVALID_METHOD: + push_error(vformat(R"(Invalid call for function "%s".)", GDScriptFunctions::get_func_name(builtin_function)), p_call); + break; + case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: + push_error(vformat(R"*(Too many arguments for "%s()" call. Expected at most %d but received %d.)*", GDScriptFunctions::get_func_name(builtin_function), err.expected, p_call->arguments.size()), p_call); + break; + case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: + push_error(vformat(R"*(Too few arguments for "%s()" call. Expected at least %d but received %d.)*", GDScriptFunctions::get_func_name(builtin_function), err.expected, p_call->arguments.size()), p_call); + break; + case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: + break; // Can't happen in a builtin constructor. + case Callable::CallError::CALL_OK: + p_call->is_constant = true; + p_call->reduced_value = value; + break; + } + } else { + validate_call_arg(function_info, p_call); + } + return; + } + } + + GDScriptParser::DataType base_type; + GDScriptParser::DataType result; + result.kind = GDScriptParser::DataType::VARIANT; + bool is_self = false; + + if (p_call->is_super) { + base_type = parser->current_class->base_type; + is_self = true; + } else if (p_call->callee->type == GDScriptParser::Node::IDENTIFIER) { + base_type = parser->current_class->get_datatype(); + is_self = true; + } else if (p_call->callee->type == GDScriptParser::Node::SUBSCRIPT) { + GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(p_call->callee); + if (!subscript->is_attribute) { + // Invalid call. + // TODO: Could check if Callable here. + p_call->set_datatype(result); + mark_node_unsafe(p_call); + return; + } + reduce_expression(subscript->base); + + base_type = subscript->base->get_datatype(); + } else { + // TODO: Could check if Callable here too. + p_call->set_datatype(result); + mark_node_unsafe(p_call); + return; + } + + bool is_static = false; + bool is_vararg = false; + int default_arg_count = 0; + GDScriptParser::DataType return_type; + List<GDScriptParser::DataType> par_types; + + if (get_function_signature(p_call, base_type, p_call->function_name, return_type, par_types, default_arg_count, is_static, is_vararg)) { + validate_call_arg(par_types, default_arg_count, is_vararg, p_call); + + if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) { + push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parser->current_function->identifier->name), p_call->callee); + } + + result = return_type; + } else { + String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string(); + push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee); + } + + p_call->set_datatype(result); +} + +void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { + reduce_expression(p_cast->operand); + + GDScriptParser::DataType cast_type = resolve_datatype(p_cast->cast_type); + + if (!cast_type.is_set()) { + return; + } + + p_cast->set_datatype(cast_type); + + if (!cast_type.is_variant()) { + if (!cast_type.is_meta_type) { + push_error(vformat(R"(Cast type "%s" isn't a valid type.)", cast_type.to_string()), p_cast->cast_type); + } else { + cast_type.is_meta_type = false; // For compatibility check purpose. + + GDScriptParser::DataType op_type = p_cast->operand->get_datatype(); + if (!op_type.is_variant()) { + bool valid = false; + if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) { + valid = Variant::can_convert(op_type.builtin_type, cast_type.builtin_type); + } else if (op_type.kind != GDScriptParser::DataType::BUILTIN && cast_type.kind != GDScriptParser::DataType::BUILTIN) { + valid = is_type_compatible(cast_type, op_type) || is_type_compatible(op_type, cast_type); + } + + if (!valid) { + push_error(vformat(R"(Invalid cast. Cannot convert from "%s" to "%s".)", op_type.to_string(), cast_type.to_string()), p_cast->cast_type); + } + } else { + mark_node_unsafe(p_cast); + } + } + } + + // TODO: Perform cast on constants. +} + +void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) { + bool all_is_constant = true; + + for (int i = 0; i < p_dictionary->elements.size(); i++) { + const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; + reduce_expression(element.key); + reduce_expression(element.value); + all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant; + } + + if (all_is_constant) { + Dictionary dict; + for (int i = 0; i < p_dictionary->elements.size(); i++) { + const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; + dict[element.key->reduced_value] = element.value->reduced_value; + } + p_dictionary->is_constant = true; + p_dictionary->reduced_value = dict; + } + + // It's dictionary in any case. + GDScriptParser::DataType dict_type; + dict_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + dict_type.kind = GDScriptParser::DataType::BUILTIN; + dict_type.builtin_type = Variant::DICTIONARY; + dict_type.is_constant = true; + + p_dictionary->set_datatype(dict_type); +} + +void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) { + GDScriptParser::DataType result; + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + result.kind = GDScriptParser::DataType::NATIVE; + result.native_type = "Node"; + result.builtin_type = Variant::OBJECT; + + if (!ClassDB::is_parent_class(get_real_class_name(parser->current_class->base_type.native_type), result.native_type)) { + push_error(R"*(Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node.)*", p_get_node); + } + + p_get_node->set_datatype(result); +} + +GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const StringName &p_class_name) { + Ref<GDScriptParserRef> ref = get_parser_for(ScriptServer::get_global_class_path(p_class_name)); + ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = GDScriptParser::DataType::CLASS; + type.builtin_type = Variant::OBJECT; + type.native_type = ScriptServer::get_global_class_native_base(p_class_name); + type.class_type = ref->get_parser()->head; + type.is_constant = true; + return type; +} + +void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType *p_base) { + GDScriptParser::DataType base; + if (p_base == nullptr) { + base = type_from_metatype(parser->current_class->get_datatype()); + } else { + base = *p_base; + } + + if (base.kind == GDScriptParser::DataType::BUILTIN && base.is_meta_type) { + bool valid = true; + Variant result = Variant::get_constant_value(base.builtin_type, p_identifier->name, &valid); + if (valid) { + p_identifier->is_constant = true; + p_identifier->reduced_value = result; + p_identifier->set_datatype(type_from_variant(result)); + } else { + push_error(vformat(R"(Cannot find constant "%s" on base "%s".)", p_identifier->name, base.to_string()), p_identifier); + } + return; + } + + const StringName &name = p_identifier->name; + GDScriptParser::ClassNode *base_class = base.class_type; + + // TODO: Switch current class/function/suite here to avoid misrepresenting identifiers (in recursive reduce calls). + while (base_class != nullptr) { + if (base_class->has_member(name)) { + const GDScriptParser::ClassNode::Member &member = base_class->get_member(name); + p_identifier->set_datatype(member.get_datatype()); + if (member.type == GDScriptParser::ClassNode::Member::CONSTANT) { + // For out-of-order resolution: + reduce_expression(member.constant->initializer); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.constant->initializer->reduced_value; + p_identifier->set_datatype(member.constant->initializer->get_datatype()); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; + p_identifier->constant_source = member.constant; + } else if (member.type == GDScriptParser::ClassNode::Member::ENUM_VALUE) { + p_identifier->is_constant = true; + p_identifier->reduced_value = member.enum_value.value; + } else if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE; + p_identifier->variable_source = member.variable; + } + return; + } + // Check outer constants. + // TODO: Allow outer static functions. + GDScriptParser::ClassNode *outer = base_class->outer; + while (outer != nullptr) { + if (outer->has_member(name)) { + const GDScriptParser::ClassNode::Member &member = base_class->get_member(name); + if (member.type == GDScriptParser::ClassNode::Member::CONSTANT) { + // TODO: Make sure loops won't cause problem. And make special error message for those. + // For out-of-order resolution: + reduce_expression(member.constant->initializer); + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.constant->initializer->reduced_value; + return; + } + } + outer = outer->outer; + } + + base_class = base_class->base_type.class_type; + } + + // Check native members. + const StringName &native = get_real_class_name(base.native_type); + + if (class_exists(native)) { + PropertyInfo prop_info; + MethodInfo method_info; + if (ClassDB::get_property_info(native, name, &prop_info)) { + p_identifier->set_datatype(type_from_property(prop_info)); + return; + } + if (ClassDB::get_method_info(native, name, &method_info)) { + // Method is callable. + p_identifier->set_datatype(make_callable_type(method_info)); + return; + } + if (ClassDB::get_signal(native, name, &method_info)) { + // Signal is a type too. + p_identifier->set_datatype(make_signal_type(method_info)); + return; + } + bool valid = false; + int 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)); + return; + } + } else { + ERR_PRINT(vformat("GDScript parser bug: Class %s isn't a native exposed class.", native.operator String())); + } +} + +void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_identifier) { + // TODO: This is opportunity to further infer types. + // Check if identifier is local. + // If that's the case, the declaration already was solved before. + switch (p_identifier->source) { + case GDScriptParser::IdentifierNode::FUNCTION_PARAMETER: + p_identifier->set_datatype(p_identifier->parameter_source->get_datatype()); + break; + case GDScriptParser::IdentifierNode::LOCAL_CONSTANT: + case GDScriptParser::IdentifierNode::MEMBER_CONSTANT: + p_identifier->set_datatype(p_identifier->constant_source->get_datatype()); + p_identifier->is_constant = true; + // TODO: Constant should have a value on the node itself. + p_identifier->reduced_value = p_identifier->constant_source->initializer->reduced_value; + return; + case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: + case GDScriptParser::IdentifierNode::MEMBER_VARIABLE: + p_identifier->set_datatype(p_identifier->variable_source->get_datatype()); + return; + case GDScriptParser::IdentifierNode::LOCAL_ITERATOR: + p_identifier->set_datatype(p_identifier->bind_source->get_datatype()); + return; + case GDScriptParser::IdentifierNode::LOCAL_BIND: { + GDScriptParser::DataType result = p_identifier->bind_source->get_datatype(); + result.is_constant = true; + p_identifier->set_datatype(result); + return; + } + case GDScriptParser::IdentifierNode::UNDEFINED_SOURCE: + break; + } + + // Not a local, so check members. + reduce_identifier_from_base(p_identifier); + if (p_identifier->get_datatype().is_set()) { + // Found. + return; + } + + StringName name = p_identifier->name; + p_identifier->source = GDScriptParser::IdentifierNode::UNDEFINED_SOURCE; + + // Check globals. + if (GDScriptParser::get_builtin_type(name) < Variant::VARIANT_MAX) { + p_identifier->set_datatype(make_builtin_meta_type(GDScriptParser::get_builtin_type(name))); + return; + } + + if (class_exists(name)) { + p_identifier->set_datatype(make_native_meta_type(name)); + return; + } + + if (ScriptServer::is_global_class(name)) { + p_identifier->set_datatype(make_global_class_meta_type(name)); + return; + } + + if (GDScriptLanguage::get_singleton()->get_global_map().has(name)) { + int idx = GDScriptLanguage::get_singleton()->get_global_map()[name]; + Variant constant = GDScriptLanguage::get_singleton()->get_global_array()[idx]; + p_identifier->set_datatype(type_from_variant(constant)); + p_identifier->is_constant = true; + p_identifier->reduced_value = constant; + return; + } + + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(name)) { + Variant constant = GDScriptLanguage::get_singleton()->get_named_globals_map()[name]; + p_identifier->set_datatype(type_from_variant(constant)); + p_identifier->is_constant = true; + p_identifier->reduced_value = constant; + return; + } + + // Not found. + push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier); + GDScriptParser::DataType dummy; + dummy.kind = GDScriptParser::DataType::VARIANT; + p_identifier->set_datatype(dummy); // Just so type is set to something. +} + +void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) { + p_literal->reduced_value = p_literal->value; + p_literal->is_constant = true; + + p_literal->set_datatype(type_from_variant(p_literal->reduced_value)); +} + +void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { + p_preload->is_constant = true; + p_preload->reduced_value = p_preload->resource; + p_preload->set_datatype(type_from_variant(p_preload->reduced_value)); +} + +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())); +} + +void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscript) { + reduce_expression(p_subscript->base); + + GDScriptParser::DataType result_type; + + // Reduce index first. If it's a constant StringName, use attribute instead. + if (!p_subscript->is_attribute) { + reduce_expression(p_subscript->index); + + if (p_subscript->index->is_constant && p_subscript->index->reduced_value.get_type() == Variant::STRING_NAME) { + GDScriptParser::IdentifierNode *attribute = parser->alloc_node<GDScriptParser::IdentifierNode>(); + // Copy location for better error message. + attribute->start_line = p_subscript->index->start_line; + attribute->end_line = p_subscript->index->end_line; + attribute->leftmost_column = p_subscript->index->leftmost_column; + attribute->rightmost_column = p_subscript->index->rightmost_column; + p_subscript->is_attribute = true; + p_subscript->attribute = attribute; + } + } + + if (p_subscript->is_attribute) { + if (p_subscript->base->is_constant) { + // Just try to get it. + bool valid = false; + Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, &valid); + if (!valid) { + push_error(vformat(R"(Cannot get member "%s" from "%s".)", p_subscript->attribute->name, p_subscript->base->reduced_value), p_subscript->index); + } else { + p_subscript->is_constant = true; + p_subscript->reduced_value = value; + result_type = type_from_variant(value); + } + result_type.kind = GDScriptParser::DataType::VARIANT; + } else { + GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); + + if (base_type.is_variant()) { + result_type.kind = GDScriptParser::DataType::VARIANT; + mark_node_unsafe(p_subscript); + } else { + reduce_identifier_from_base(p_subscript->attribute, &base_type); + GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype(); + if (attr_type.is_set()) { + result_type = attr_type; + p_subscript->is_constant = p_subscript->attribute->is_constant; + p_subscript->reduced_value = p_subscript->attribute->reduced_value; + } else { + push_error(vformat(R"(Cannot find member "%s" in base "%s".)", p_subscript->attribute->name, base_type.to_string()), p_subscript->attribute); + result_type.kind = GDScriptParser::DataType::VARIANT; + } + } + } + } else { + // Index was already reduced before. + + if (p_subscript->base->is_constant && p_subscript->index->is_constant) { + // Just try to get it. + bool valid = false; + Variant value = p_subscript->base->reduced_value.get(p_subscript->index->reduced_value, &valid); + if (!valid) { + push_error(vformat(R"(Cannot get index "%s" from "%s".)", p_subscript->index->reduced_value, p_subscript->base->reduced_value), p_subscript->index); + } else { + p_subscript->is_constant = true; + p_subscript->reduced_value = value; + result_type = type_from_variant(value); + } + result_type.kind = GDScriptParser::DataType::VARIANT; + } else { + GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); + GDScriptParser::DataType index_type = p_subscript->index->get_datatype(); + + if (base_type.is_variant()) { + result_type.kind = GDScriptParser::DataType::VARIANT; + mark_node_unsafe(p_subscript); + } else { + if (base_type.kind == GDScriptParser::DataType::BUILTIN && !index_type.is_variant()) { + // Check if indexing is valid. + bool error = index_type.kind != GDScriptParser::DataType::BUILTIN && base_type.builtin_type != Variant::DICTIONARY; + if (!error) { + switch (base_type.builtin_type) { + // Expect int or real as index. + case Variant::PACKED_BYTE_ARRAY: + case Variant::PACKED_COLOR_ARRAY: + case Variant::PACKED_FLOAT32_ARRAY: + case Variant::PACKED_FLOAT64_ARRAY: + case Variant::PACKED_INT32_ARRAY: + case Variant::PACKED_INT64_ARRAY: + case Variant::PACKED_STRING_ARRAY: + case Variant::PACKED_VECTOR2_ARRAY: + case Variant::PACKED_VECTOR3_ARRAY: + case Variant::ARRAY: + case Variant::STRING: + error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT; + break; + // Expect String only. + case Variant::RECT2: + case Variant::RECT2I: + case Variant::PLANE: + case Variant::QUAT: + case Variant::AABB: + case Variant::OBJECT: + error = index_type.builtin_type != Variant::STRING; + break; + // Expect String or number. + case Variant::BASIS: + case Variant::VECTOR2: + case Variant::VECTOR2I: + case Variant::VECTOR3: + case Variant::VECTOR3I: + case Variant::TRANSFORM: + case Variant::TRANSFORM2D: + error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT && + index_type.builtin_type != Variant::STRING; + break; + // Expect String or int. + case Variant::COLOR: + error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING; + break; + // Don't support indexing, but we will check it later. + case Variant::_RID: + case Variant::BOOL: + case Variant::CALLABLE: + case Variant::FLOAT: + case Variant::INT: + case Variant::NIL: + case Variant::NODE_PATH: + case Variant::SIGNAL: + case Variant::STRING_NAME: + break; + // Here for completeness. + case Variant::DICTIONARY: + case Variant::VARIANT_MAX: + break; + } + + if (error) { + push_error(vformat(R"(Invalid index type "%s" for a base of type "%s".)", index_type.to_string(), base_type.to_string()), p_subscript->index); + } + } + } else if (base_type.kind != GDScriptParser::DataType::BUILTIN && !index_type.is_variant()) { + if (index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME) { + push_error(vformat(R"(Only String or StringName can be used as index for type "%s", but received a "%s".)", base_type.to_string(), index_type.to_string()), p_subscript->index); + } + } + + // Check resulting type if possible. + result_type.builtin_type = Variant::NIL; + result_type.kind = GDScriptParser::DataType::BUILTIN; + result_type.type_source = GDScriptParser::DataType::INFERRED; + + switch (base_type.builtin_type) { + // Can't index at all. + case Variant::_RID: + case Variant::BOOL: + case Variant::CALLABLE: + case Variant::FLOAT: + case Variant::INT: + case Variant::NIL: + case Variant::NODE_PATH: + case Variant::SIGNAL: + case Variant::STRING_NAME: + result_type.kind = GDScriptParser::DataType::VARIANT; + push_error(vformat(R"(Cannot use subscript operator on a base of type "%s".)", base_type.to_string()), p_subscript->base); + break; + // Return int. + case Variant::PACKED_BYTE_ARRAY: + case Variant::PACKED_INT32_ARRAY: + case Variant::PACKED_INT64_ARRAY: + case Variant::VECTOR2I: + case Variant::VECTOR3I: + result_type.builtin_type = Variant::INT; + break; + // Return float. + case Variant::PACKED_FLOAT32_ARRAY: + case Variant::PACKED_FLOAT64_ARRAY: + case Variant::VECTOR2: + case Variant::VECTOR3: + case Variant::QUAT: + result_type.builtin_type = Variant::FLOAT; + break; + // Return Color. + case Variant::PACKED_COLOR_ARRAY: + result_type.builtin_type = Variant::COLOR; + break; + // Return String. + case Variant::PACKED_STRING_ARRAY: + case Variant::STRING: + result_type.builtin_type = Variant::STRING; + break; + // Return Vector2. + case Variant::PACKED_VECTOR2_ARRAY: + case Variant::TRANSFORM2D: + case Variant::RECT2: + result_type.builtin_type = Variant::VECTOR2; + break; + // Return Vector2I. + case Variant::RECT2I: + result_type.builtin_type = Variant::VECTOR2I; + break; + // Return Vector3. + case Variant::PACKED_VECTOR3_ARRAY: + case Variant::AABB: + case Variant::BASIS: + result_type.builtin_type = Variant::VECTOR3; + break; + // Depends on the index. + case Variant::TRANSFORM: + case Variant::PLANE: + case Variant::COLOR: + case Variant::ARRAY: + case Variant::DICTIONARY: + result_type.kind = GDScriptParser::DataType::VARIANT; + result_type.type_source = GDScriptParser::DataType::UNDETECTED; + break; + // Here for completeness. + case Variant::OBJECT: + case Variant::VARIANT_MAX: + break; + } + } + } + } + + p_subscript->set_datatype(result_type); +} + +void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op) { + reduce_expression(p_ternary_op->condition); + reduce_expression(p_ternary_op->true_expr); + reduce_expression(p_ternary_op->false_expr); + + GDScriptParser::DataType result; + + if (p_ternary_op->condition->is_constant && p_ternary_op->true_expr->is_constant && p_ternary_op->false_expr->is_constant) { + p_ternary_op->is_constant = true; + if (p_ternary_op->condition->reduced_value.booleanize()) { + p_ternary_op->reduced_value = p_ternary_op->true_expr->reduced_value; + } else { + p_ternary_op->reduced_value = p_ternary_op->false_expr->reduced_value; + } + } + + GDScriptParser::DataType true_type = p_ternary_op->true_expr->get_datatype(); + GDScriptParser::DataType false_type = p_ternary_op->false_expr->get_datatype(); + + if (true_type.is_variant() || false_type.is_variant()) { + result.kind = GDScriptParser::DataType::VARIANT; + } else { + result = true_type; + if (!is_type_compatible(true_type, false_type)) { + result = false_type; + if (!is_type_compatible(false_type, true_type)) { + // TODO: Add warning here. Types of arms are not compatible with each other. + result.type_source = GDScriptParser::DataType::UNDETECTED; + result.kind = GDScriptParser::DataType::VARIANT; + } + } + } + + p_ternary_op->set_datatype(result); +} + +void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) { + reduce_expression(p_unary_op->operand); + + GDScriptParser::DataType result; + + 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()); + result = type_from_variant(p_unary_op->reduced_value); + } else if (p_unary_op->operand->get_datatype().is_variant()) { + result.kind = GDScriptParser::DataType::VARIANT; + mark_node_unsafe(p_unary_op); + } else { + bool valid = false; + result = get_operation_type(p_unary_op->variant_op, p_unary_op->operand->get_datatype(), p_unary_op->operand->get_datatype(), valid); + + if (!valid) { + push_error(vformat(R"(Invalid operand of type "%s" for unary operator "%s".)", p_unary_op->operand->get_datatype().to_string(), Variant::get_operator_name(p_unary_op->variant_op)), p_unary_op->operand); + } + } + + p_unary_op->set_datatype(result); +} + +GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_value) { + GDScriptParser::DataType result; + result.is_constant = true; + result.kind = GDScriptParser::DataType::BUILTIN; + result.builtin_type = p_value.get_type(); + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; // Constant has explicit type. + + if (p_value.get_type() == Variant::OBJECT) { + Object *obj = p_value; + if (!obj) { + return GDScriptParser::DataType(); + } + result.native_type = obj->get_class_name(); + + Ref<Script> scr = p_value; // Check if value is a script itself. + if (scr.is_valid()) { + result.is_meta_type = true; + } else { + result.is_meta_type = false; + scr = obj->get_script(); + } + if (scr.is_valid()) { + result.script_type = scr; + Ref<GDScript> gds = scr; + if (gds.is_valid()) { + result.kind = GDScriptParser::DataType::CLASS; + Ref<GDScriptParserRef> ref = get_parser_for(gds->get_path()); + ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + result.class_type = ref->get_parser()->head; + } else { + result.kind = GDScriptParser::DataType::SCRIPT; + } + result.native_type = scr->get_instance_base_type(); + } else { + result.kind = GDScriptParser::DataType::NATIVE; + if (result.native_type == GDScriptNativeClass::get_class_static()) { + result.is_meta_type = true; + } + } + } + + return result; +} + +GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptParser::DataType &p_meta_type) const { + GDScriptParser::DataType result = p_meta_type; + result.is_meta_type = false; + result.is_constant = false; + return result; +} + +GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo &p_property) const { + GDScriptParser::DataType result; + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + if (p_property.type == Variant::NIL && (p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) { + // Variant + result.kind = GDScriptParser::DataType::VARIANT; + return result; + } + result.builtin_type = p_property.type; + if (p_property.type == Variant::OBJECT) { + result.kind = GDScriptParser::DataType::NATIVE; + result.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; + } else { + result.kind = GDScriptParser::DataType::BUILTIN; + } + return result; +} + +bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) { + r_static = false; + r_vararg = false; + r_default_arg_count = 0; + + if (p_base_type.kind == GDScriptParser::DataType::BUILTIN) { + // Construct a base type to get methods. + Callable::CallError err; + Variant dummy = Variant::construct(p_base_type.builtin_type, nullptr, 0, err); + if (err.error != Callable::CallError::CALL_OK) { + ERR_FAIL_V_MSG(false, "Could not construct base Variant type."); + } + List<MethodInfo> methods; + dummy.get_method_list(&methods); + + for (const List<MethodInfo>::Element *E = methods.front(); E != nullptr; E = E->next()) { + if (E->get().name == p_function) { + return function_signature_from_info(E->get(), r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + } + } + + return false; + } + + GDScriptParser::ClassNode *base_class = p_base_type.class_type; + GDScriptParser::FunctionNode *found_function = nullptr; + + while (found_function == nullptr && base_class != nullptr) { + if (base_class->has_member(p_function)) { + if (base_class->get_member(p_function).type != GDScriptParser::ClassNode::Member::FUNCTION) { + // TODO: If this is Callable it can have a better error message. + push_error(vformat(R"(Member "%s" is not a function.)", p_function), p_source); + return false; + } + found_function = base_class->get_member(p_function).function; + } + base_class = base_class->base_type.class_type; + } + + if (found_function != nullptr) { + r_static = found_function->is_static; + for (int i = 0; i < found_function->parameters.size(); i++) { + r_par_types.push_back(found_function->parameters[i]->get_datatype()); + if (found_function->parameters[i]->default_value != nullptr) { + r_default_arg_count++; + } + } + r_return_type = found_function->get_datatype(); + r_return_type.is_coroutine = found_function->is_coroutine; + + return true; + } + + // TODO: Check here the "new" function in GDScript class when metatype, as it needs to look the constructor. + + Ref<Script> base_script = p_base_type.script_type; + + while (base_script.is_valid() && base_script->is_valid()) { + MethodInfo info = base_script->get_method_info(p_function); + + if (!(info == MethodInfo())) { + return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + } + base_script = base_script->get_base_script(); + } + + // If the base is a script, it might be trying to access members of the Script class itself. + if (p_base_type.is_meta_type && !(p_function == "new") && (p_base_type.kind == GDScriptParser::DataType::SCRIPT || p_base_type.kind == GDScriptParser::DataType::CLASS)) { + MethodInfo info; + StringName script_class = p_base_type.kind == GDScriptParser::DataType::SCRIPT ? p_base_type.script_type->get_class_name() : StringName(GDScript::get_class_static()); + + if (ClassDB::get_method_info(script_class, p_function, &info)) { + return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + } + } + + StringName base_native = p_base_type.native_type; + if (base_native == StringName()) { + // Empty native class, might happen in some Script implementations. + // Just ignore it. + return false; + } + +#ifdef DEBUG_ENABLED + if (!class_exists(base_native)) { + ERR_FAIL_V_MSG(false, vformat("Native class %s used in script doesn't exist or isn't exposed.", base_native)); + } +#endif + + StringName real_native = get_real_class_name(base_native); + + MethodInfo info; + if (ClassDB::get_method_info(real_native, p_function, &info)) { + return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + } + + return false; +} + +bool GDScriptAnalyzer::function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) { + r_return_type = type_from_property(p_info.return_val); + r_default_arg_count = p_info.default_arguments.size(); + r_vararg = (p_info.flags & METHOD_FLAG_VARARG) != 0; + + for (const List<PropertyInfo>::Element *E = p_info.arguments.front(); E != nullptr; E = E->next()) { + r_par_types.push_back(type_from_property(E->get())); + } + return true; +} + +bool GDScriptAnalyzer::validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call) { + List<GDScriptParser::DataType> arg_types; + + for (const List<PropertyInfo>::Element *E = p_method.arguments.front(); E != nullptr; E = E->next()) { + arg_types.push_back(type_from_property(E->get())); + } + + return validate_call_arg(arg_types, p_method.default_arguments.size(), (p_method.flags & METHOD_FLAG_VARARG) != 0, p_call); +} + +bool GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call) { + bool valid = true; + + if (p_call->arguments.size() < p_par_types.size() - p_default_args_count) { + push_error(vformat(R"*(Too few arguments for "%s()" call. Expected at least %d but received %d.)*", p_call->function_name, p_par_types.size() - p_default_args_count, p_call->arguments.size()), p_call); + valid = false; + } + if (!p_is_vararg && p_call->arguments.size() > p_par_types.size()) { + push_error(vformat(R"*(Too many arguments for "%s()" call. Expected at most %d but received %d.)*", p_call->function_name, p_par_types.size(), p_call->arguments.size()), p_call->arguments[p_par_types.size()]); + valid = false; + } + + for (int i = 0; i < p_call->arguments.size(); i++) { + if (i >= p_par_types.size()) { + // Already on vararg place. + break; + } + GDScriptParser::DataType par_type = p_par_types[i]; + GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype(); + + if (arg_type.kind == GDScriptParser::DataType::VARIANT) { + // Argument can be anything, so this is unsafe. + mark_node_unsafe(p_call->arguments[i]); + } else if (!is_type_compatible(par_type, arg_type, true)) { + // Supertypes are acceptable for dynamic compliance, but it's unsafe. + mark_node_unsafe(p_call); + if (!is_type_compatible(arg_type, par_type)) { + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", + p_call->function_name, i + 1, par_type.to_string(), arg_type.to_string()), + p_call->arguments[i]); + valid = false; + } + } else { + if (par_type.kind == GDScriptParser::DataType::BUILTIN && par_type.builtin_type == Variant::INT && arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == Variant::FLOAT) { + // TODO: Add narrowing conversion warning. + } + } + } + return valid; +} + +GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid) { + // This function creates dummy variant values and apply the operation to those. Less error-prone than keeping a table of valid operations. + + GDScriptParser::DataType result; + result.kind = GDScriptParser::DataType::VARIANT; + + Variant::Type a_type = p_a.builtin_type; + Variant::Type b_type = p_b.builtin_type; + + Variant a; + REF a_ref; + if (a_type == Variant::OBJECT) { + a_ref.instance(); + a = a_ref; + } else { + Callable::CallError err; + a = Variant::construct(a_type, nullptr, 0, err); + if (err.error != Callable::CallError::CALL_OK) { + r_valid = false; + ERR_FAIL_V_MSG(result, vformat("Could not construct value of type %s", Variant::get_type_name(a_type))); + } + } + Variant b; + REF b_ref; + if (b_type == Variant::OBJECT) { + b_ref.instance(); + b = b_ref; + } else { + Callable::CallError err; + b = Variant::construct(b_type, nullptr, 0, err); + if (err.error != Callable::CallError::CALL_OK) { + r_valid = false; + ERR_FAIL_V_MSG(result, vformat("Could not construct value of type %s", Variant::get_type_name(b_type))); + } + } + + // Avoid division by zero. + switch (b_type) { + case Variant::INT: + b = 1; + break; + case Variant::FLOAT: + b = 1.0; + break; + case Variant::VECTOR2: + b = Vector2(1.0, 1.0); + break; + case Variant::VECTOR2I: + b = Vector2i(1, 1); + break; + case Variant::VECTOR3: + b = Vector3(1.0, 1.0, 1.0); + break; + case Variant::VECTOR3I: + b = Vector3i(1, 1, 1); + break; + case Variant::COLOR: + b = Color(1.0, 1.0, 1.0, 1.0); + break; + default: + // No change needed. + break; + } + + // Avoid error in formatting operator (%) where it doesn't find a placeholder. + if (a_type == Variant::STRING) { + a = String("%s"); + } + + Variant ret; + Variant::evaluate(p_operation, a, b, ret, r_valid); + + if (r_valid) { + return type_from_variant(ret); + } + + return result; +} + +// TODO: Add safe/unsafe return variable (for variant cases) +bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion) const { + // if (!p_target.is_set()){ + // print_line("target not set"); + // } + // if (!p_source.is_set()){ + // print_line("source not set"); + // } + // These return "true" so it doesn't affect users negatively. + ERR_FAIL_COND_V_MSG(!p_target.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset target type"); + ERR_FAIL_COND_V_MSG(!p_source.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset value type"); + + if (p_target.kind == GDScriptParser::DataType::VARIANT) { + // Variant can receive anything. + return true; + } + + if (p_source.kind == GDScriptParser::DataType::VARIANT) { + // TODO: This is acceptable but unsafe. Make sure unsafe line is set. + return true; + } + + if (p_target.kind == GDScriptParser::DataType::BUILTIN) { + bool valid = p_source.kind == GDScriptParser::DataType::BUILTIN && p_target.builtin_type == p_source.builtin_type; + if (!valid && p_allow_implicit_conversion) { + valid = Variant::can_convert_strict(p_source.builtin_type, p_target.builtin_type); + } + return valid; + } + + // From here on the target type is an object, so we have to test polymorphism. + + if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::NIL) { + // null is acceptable in object. + return true; + } + + StringName src_native; + Ref<Script> src_script; + const GDScriptParser::ClassNode *src_class = nullptr; + + switch (p_source.kind) { + case GDScriptParser::DataType::NATIVE: + if (p_target.kind != GDScriptParser::DataType::NATIVE) { + // Non-native class cannot be supertype of native. + return false; + } + if (p_source.is_meta_type) { + src_native = GDScriptNativeClass::get_class_static(); + } else { + src_native = p_source.native_type; + } + break; + case GDScriptParser::DataType::SCRIPT: + if (p_target.kind == GDScriptParser::DataType::CLASS) { + // A script type cannot be a subtype of a GDScript class. + return false; + } + if (p_source.is_meta_type) { + src_native = p_source.script_type->get_class_name(); + } else { + src_script = p_source.script_type; + src_native = src_script->get_instance_base_type(); + } + break; + case GDScriptParser::DataType::CLASS: + if (p_source.is_meta_type) { + src_native = GDScript::get_class_static(); + } else { + src_class = p_source.class_type; + const GDScriptParser::ClassNode *base = src_class; + while (base->base_type.kind == GDScriptParser::DataType::CLASS) { + base = base->base_type.class_type; + } + src_native = base->base_type.native_type; + src_script = base->base_type.script_type; + } + break; + case GDScriptParser::DataType::VARIANT: + case GDScriptParser::DataType::BUILTIN: + case GDScriptParser::DataType::UNRESOLVED: + break; // Already solved before. + } + + // Get underscore-prefixed version for some classes. + src_native = get_real_class_name(src_native); + + switch (p_target.kind) { + case GDScriptParser::DataType::NATIVE: { + if (p_target.is_meta_type) { + return ClassDB::is_parent_class(src_native, GDScriptNativeClass::get_class_static()); + } + StringName tgt_native = get_real_class_name(p_target.native_type); + return ClassDB::is_parent_class(src_native, tgt_native); + } + case GDScriptParser::DataType::SCRIPT: + if (p_target.is_meta_type) { + return ClassDB::is_parent_class(src_native, p_target.script_type->get_class_name()); + } + while (src_script.is_valid()) { + if (src_script == p_target.script_type) { + return true; + } + src_script = src_script->get_base_script(); + } + return false; + case GDScriptParser::DataType::CLASS: + if (p_target.is_meta_type) { + return ClassDB::is_parent_class(src_native, GDScript::get_class_static()); + } + while (src_class != nullptr) { + if (src_class->fqcn == p_target.class_type->fqcn) { + return true; + } + src_class = src_class->base_type.class_type; + } + return false; + case GDScriptParser::DataType::VARIANT: + case GDScriptParser::DataType::BUILTIN: + case GDScriptParser::DataType::UNRESOLVED: + break; // Already solved before. + } + + return false; +} + +void GDScriptAnalyzer::push_error(const String &p_message, const GDScriptParser::Node *p_origin) { + mark_node_unsafe(p_origin); + parser->push_error(p_message, p_origin); +} + +void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) { + for (int i = p_node->start_line; i <= p_node->end_line; i++) { + parser->unsafe_lines.insert(i); + } +} + +bool GDScriptAnalyzer::class_exists(const StringName &p_class) { + StringName real_name = get_real_class_name(p_class); + return ClassDB::class_exists(real_name) && ClassDB::is_class_exposed(real_name); +} + +Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) { + Ref<GDScriptParserRef> ref; + if (depended_parsers.has(p_path)) { + ref = depended_parsers[p_path]; + } else { + Error err = OK; + ref = GDScriptCache::get_parser(p_path, GDScriptParserRef::EMPTY, err); + depended_parsers[p_path] = ref; + } + + return ref; +} + +Error GDScriptAnalyzer::resolve_inheritance() { + return resolve_inheritance(parser->head); +} + +Error GDScriptAnalyzer::resolve_interface() { + resolve_class_interface(parser->head); + return parser->errors.empty() ? OK : ERR_PARSE_ERROR; +} + +Error GDScriptAnalyzer::resolve_body() { + resolve_class_body(parser->head); + return parser->errors.empty() ? OK : ERR_PARSE_ERROR; +} - return parser->errors.size() > 0 ? ERR_PARSE_ERROR : OK; +Error GDScriptAnalyzer::resolve_program() { + resolve_class_interface(parser->head); + resolve_class_body(parser->head); + return parser->errors.empty() ? OK : ERR_PARSE_ERROR; } Error GDScriptAnalyzer::analyze() { @@ -275,7 +2494,7 @@ Error GDScriptAnalyzer::analyze() { if (err) { return err; } - return resolve_datatypes(parser->head); + return resolve_program(); } GDScriptAnalyzer::GDScriptAnalyzer(GDScriptParser *p_parser) { diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 33f036f11c..2794e55b3d 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -31,19 +31,82 @@ #ifndef GDSCRIPT_ANALYZER_H #define GDSCRIPT_ANALYZER_H +#include "core/object.h" +#include "core/reference.h" +#include "core/set.h" +#include "gdscript_cache.h" #include "gdscript_parser.h" class GDScriptAnalyzer { GDScriptParser *parser = nullptr; + HashMap<String, Ref<GDScriptParserRef>> depended_parsers; Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true); GDScriptParser::DataType resolve_datatype(const GDScriptParser::TypeNode *p_type); + void decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement); + // This traverses the tree to resolve all TypeNodes. - Error resolve_datatypes(GDScriptParser::ClassNode *p_class); + Error resolve_program(); + + void resolve_annotation(GDScriptParser::AnnotationNode *p_annotation); + void resolve_class_interface(GDScriptParser::ClassNode *p_class); + void resolve_class_body(GDScriptParser::ClassNode *p_class); + void resolve_function_signature(GDScriptParser::FunctionNode *p_function); + void resolve_function_body(GDScriptParser::FunctionNode *p_function); + void resolve_node(GDScriptParser::Node *p_node); + void resolve_suite(GDScriptParser::SuiteNode *p_suite); + void resolve_if(GDScriptParser::IfNode *p_if); + void resolve_for(GDScriptParser::ForNode *p_for); + void resolve_while(GDScriptParser::WhileNode *p_while); + void resolve_variable(GDScriptParser::VariableNode *p_variable); + void resolve_constant(GDScriptParser::ConstantNode *p_constant); + void resolve_assert(GDScriptParser::AssertNode *p_assert); + void resolve_match(GDScriptParser::MatchNode *p_match); + void resolve_match_branch(GDScriptParser::MatchBranchNode *p_match_branch, GDScriptParser::ExpressionNode *p_match_test); + void resolve_match_pattern(GDScriptParser::PatternNode *p_match_pattern, GDScriptParser::ExpressionNode *p_match_test); + void resolve_pararameter(GDScriptParser::ParameterNode *p_parameter); + void resolve_return(GDScriptParser::ReturnNode *p_return); + + // Reduction functions. + void reduce_expression(GDScriptParser::ExpressionNode *p_expression); + void reduce_array(GDScriptParser::ArrayNode *p_array); + void reduce_assignment(GDScriptParser::AssignmentNode *p_assignment); + void reduce_await(GDScriptParser::AwaitNode *p_await); + void reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_op); + void reduce_call(GDScriptParser::CallNode *p_call); + void reduce_cast(GDScriptParser::CastNode *p_cast); + void reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary); + void reduce_get_node(GDScriptParser::GetNodeNode *p_get_node); + void reduce_identifier(GDScriptParser::IdentifierNode *p_identifier); + void reduce_identifier_from_base(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType *p_base = nullptr); + void reduce_literal(GDScriptParser::LiteralNode *p_literal); + void reduce_preload(GDScriptParser::PreloadNode *p_preload); + void reduce_self(GDScriptParser::SelfNode *p_self); + void reduce_subscript(GDScriptParser::SubscriptNode *p_subscript); + void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op); + void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op); + + // Helpers. + GDScriptParser::DataType type_from_variant(const Variant &p_value); + GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type) const; + GDScriptParser::DataType type_from_property(const PropertyInfo &p_property) const; + GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name); + bool get_function_signature(GDScriptParser::Node *p_source, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); + bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); + bool validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call); + bool validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call); + GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid); + bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false) const; + void push_error(const String &p_message, const GDScriptParser::Node *p_origin); + void mark_node_unsafe(const GDScriptParser::Node *p_node); + bool class_exists(const StringName &p_class); + Ref<GDScriptParserRef> get_parser_for(const String &p_path); public: Error resolve_inheritance(); + Error resolve_interface(); + Error resolve_body(); Error analyze(); GDScriptAnalyzer(GDScriptParser *p_parser); diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 8c83f712ad..80efc9519d 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -137,7 +137,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } break; case GDScriptParser::DataType::CLASS: { // Locate class by constructing the path to it and following that path - GDScriptParser::ClassNode *class_type = p_datatype.gdscript_type; + GDScriptParser::ClassNode *class_type = p_datatype.class_type; if (class_type) { List<StringName> names; while (class_type->outer) { @@ -233,6 +233,10 @@ int GDScriptCompiler::_parse_assign_right_expression(CodeGen &codegen, const GDS } int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_expression, int p_stack_level, bool p_root, bool p_initializer, int p_index_addr) { + if (p_expression->is_constant) { + return codegen.get_constant_pos(p_expression->reduced_value); + } + switch (p_expression->type) { //should parse variable declaration and adjust stack accordingly... case GDScriptParser::Node::IDENTIFIER: { @@ -2578,14 +2582,14 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->_base = base.ptr(); p_script->member_indices = base->member_indices; - if (p_class->base_type.kind == GDScriptParser::DataType::CLASS && p_class->base_type.gdscript_type != nullptr) { + if (p_class->base_type.kind == GDScriptParser::DataType::CLASS && p_class->base_type.class_type != nullptr) { if (!parsed_classes.has(p_script->_base)) { if (parsing_classes.has(p_script->_base)) { String class_name = p_class->identifier ? p_class->identifier->name : "<main>"; _set_error("Cyclic class reference for '" + class_name + "'.", p_class); return ERR_PARSE_ERROR; } - Error err = _parse_class_level(p_script->_base, p_class->base_type.gdscript_type, p_keep_state); + Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state); if (err) { return err; } diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 5cbd8afc07..e5d72c31da 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -33,6 +33,7 @@ #include "core/engine.h" #include "core/global_constants.h" #include "core/os/file_access.h" +#include "gdscript_analyzer.h" #include "gdscript_compiler.h" #include "gdscript_parser.h" #include "gdscript_tokenizer.h" @@ -130,6 +131,7 @@ static void get_function_names_recursively(const GDScriptParser::ClassNode *p_cl bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, List<ScriptLanguage::Warning> *r_warnings, Set<int> *r_safe_lines) const { GDScriptParser parser; + GDScriptAnalyzer analyzer(&parser); Error err = parser.parse(p_script, p_path, false); #ifdef DEBUG_ENABLED @@ -146,6 +148,9 @@ bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int & // } // } #endif + if (err == OK) { + err = analyzer.analyze(); + } if (err) { GDScriptParser::ParserError parse_error = parser.get_errors().front()->get(); r_line_error = parse_error.line; diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp index d4258c385e..7f2a62a8e9 100644 --- a/modules/gdscript/gdscript_functions.cpp +++ b/modules/gdscript/gdscript_functions.cpp @@ -146,12 +146,14 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ if (p_arg_count < m_count) { \ r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \ r_error.argument = m_count; \ + r_error.expected = m_count; \ r_ret = Variant(); \ return; \ } \ if (p_arg_count > m_count) { \ r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \ r_error.argument = m_count; \ + r_error.expected = m_count; \ r_ret = Variant(); \ return; \ } @@ -897,6 +899,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ case 0: { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_error.argument = 1; + r_error.expected = 1; r_ret = Variant(); } break; @@ -1001,6 +1004,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ default: { r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; r_error.argument = 3; + r_error.expected = 3; r_ret = Variant(); } break; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index fe056c26ad..dadd97fbbd 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -1894,6 +1894,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_await(ExpressionNode *p_pr AwaitNode *await = alloc_node<AwaitNode>(); await->to_await = parse_precedence(PREC_AWAIT, false); + current_function->is_coroutine = true; + return await; } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index ec5e78bd4f..23b8fc249c 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -127,6 +127,7 @@ public: _FORCE_INLINE_ bool is_set() const { return kind != UNRESOLVED; } _FORCE_INLINE_ bool has_no_type() const { return type_source == UNDETECTED; } _FORCE_INLINE_ bool is_variant() const { return kind == VARIANT; } + _FORCE_INLINE_ bool is_hard_type() const { return type_source > INFERRED; } String to_string() const; bool operator==(const DataType &p_other) const { @@ -591,6 +592,7 @@ public: TypeNode *return_type = nullptr; SuiteNode *body = nullptr; bool is_static = false; + bool is_coroutine = false; MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; bool resolved_signature = false; @@ -620,7 +622,8 @@ public: LOCAL_VARIABLE, LOCAL_ITERATOR, // `for` loop iterator. LOCAL_BIND, // Pattern bind. - // TODO: Add higher sources to help compiling? + MEMBER_VARIABLE, + MEMBER_CONSTANT, }; Source source = UNDEFINED_SOURCE; |