diff options
author | George Marques <george@gmarqu.es> | 2020-07-15 22:02:44 -0300 |
---|---|---|
committer | George Marques <george@gmarqu.es> | 2020-07-22 11:07:51 -0300 |
commit | a0f54cb95e3a0250df8ed6e6a54720c487034d97 (patch) | |
tree | 6beb7819395100a5045937a10ee33e4d4bf67f36 | |
parent | aa09b4f85d94b9d563a0b2cbaa399427527ce6fc (diff) |
Wrap up GDScript 2.0 base implementation
-rw-r--r-- | modules/gdscript/gdscript.cpp | 35 | ||||
-rw-r--r-- | modules/gdscript/gdscript_analyzer.cpp | 405 | ||||
-rw-r--r-- | modules/gdscript/gdscript_analyzer.h | 4 | ||||
-rw-r--r-- | modules/gdscript/gdscript_cache.cpp | 24 | ||||
-rw-r--r-- | modules/gdscript/gdscript_cache.h | 2 | ||||
-rw-r--r-- | modules/gdscript/gdscript_compiler.cpp | 292 | ||||
-rw-r--r-- | modules/gdscript/gdscript_compiler.h | 1 | ||||
-rw-r--r-- | modules/gdscript/gdscript_editor.cpp | 6 | ||||
-rw-r--r-- | modules/gdscript/gdscript_parser.cpp | 108 | ||||
-rw-r--r-- | modules/gdscript/gdscript_parser.h | 36 | ||||
-rw-r--r-- | modules/gdscript/gdscript_tokenizer.cpp | 3 | ||||
-rw-r--r-- | modules/gdscript/gdscript_tokenizer.h | 1 | ||||
-rw-r--r-- | modules/gdscript/language_server/gdscript_extend_parser.cpp | 6 |
13 files changed, 590 insertions, 333 deletions
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 0eade062c9..40ef0aeec6 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1873,6 +1873,8 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { "func", "preload", "signal", + "super", + "trait", "yield", // var "const", @@ -2081,36 +2083,28 @@ RES ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_ori *r_error = ERR_FILE_CANT_OPEN; } - GDScript *script = memnew(GDScript); - - Ref<GDScript> scriptres(script); - - if (p_path.ends_with(".gde") || p_path.ends_with(".gdc")) { - script->set_script_path(p_original_path); // script needs this. - script->set_path(p_original_path); - Error err = script->load_byte_code(p_path); - ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load byte code from file '" + p_path + "'."); - - } else { - Error err = script->load_source_code(p_path); - ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load source code from file '" + p_path + "'."); + Error err; + Ref<GDScript> script = GDScriptCache::get_full_script(p_path, err); - script->set_script_path(p_original_path); // script needs this. - script->set_path(p_original_path); + // TODO: Reintroduce binary and encrypted scripts. - script->reload(); + if (script.is_null()) { + // Don't fail loading because of parsing error. + script.instance(); } + if (r_error) { *r_error = OK; } - return scriptres; + return script; } void ResourceFormatLoaderGDScript::get_recognized_extensions(List<String> *p_extensions) const { p_extensions->push_back("gd"); - p_extensions->push_back("gdc"); - p_extensions->push_back("gde"); + // TODO: Reintroduce binary and encrypted scripts. + // p_extensions->push_back("gdc"); + // p_extensions->push_back("gde"); } bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const { @@ -2119,7 +2113,8 @@ bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const { String ResourceFormatLoaderGDScript::get_resource_type(const String &p_path) const { String el = p_path.get_extension().to_lower(); - if (el == "gd" || el == "gdc" || el == "gde") { + // TODO: Reintroduce binary and encrypted scripts. + if (el == "gd" /*|| el == "gdc" || el == "gde"*/) { return "GDScript"; } return ""; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 7201490cee..51d93481b6 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -144,7 +144,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, p_class->fqcn = parser->script_path; } } else { - p_class->fqcn = p_class->outer->fqcn + "." + String(p_class->identifier->name); + p_class->fqcn = p_class->outer->fqcn + "::" + String(p_class->identifier->name); } GDScriptParser::DataType result; @@ -156,6 +156,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, class_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; class_type.kind = GDScriptParser::DataType::CLASS; class_type.class_type = p_class; + class_type.script_path = parser->script_path; p_class->set_datatype(class_type); if (!p_class->extends_used) { @@ -184,22 +185,29 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, base = parser->get_parser()->head->get_datatype(); } else { + if (p_class->extends.empty()) { + return ERR_PARSE_ERROR; + } const StringName &name = p_class->extends[extends_index++]; base.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; if (ScriptServer::is_global_class(name)) { 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; - } + if (base_path == parser->script_path) { + base = parser->head->get_datatype(); + } else { + 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; + 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); @@ -224,13 +232,29 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, base.native_type = name; } else { // Look for other classes in script. - const GDScriptParser::ClassNode *look_class = p_class; + 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; + if (look_class->identifier && look_class->identifier->name == name) { + if (!look_class->get_datatype().is_set()) { + Error err = resolve_inheritance(look_class, false); + if (err) { + return err; + } + } + base = look_class->get_datatype(); + found = true; + break; + } + if (look_class->members_indices.has(name) && look_class->get_member(name).type == GDScriptParser::ClassNode::Member::CLASS) { + GDScriptParser::ClassNode::Member member = look_class->get_member(name); + if (!member.m_class->get_datatype().is_set()) { + Error err = resolve_inheritance(member.m_class, false); + if (err) { + return err; + } + } + base = member.m_class->get_datatype(); found = true; break; } @@ -331,12 +355,16 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type result.kind = GDScriptParser::DataType::NATIVE; result.native_type = first; } else if (ScriptServer::is_global_class(first)) { - 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(); + if (parser->script_path == ScriptServer::get_global_class_path(first)) { + result = parser->head->get_datatype(); + } else { + 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(); } - 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); @@ -353,12 +381,16 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type GDScriptParser::ClassNode *script_class = parser->current_class; bool found = false; while (!found && script_class != nullptr) { + if (script_class->identifier && script_class->identifier->name == first) { + result = script_class->get_datatype(); + found = true; + break; + } if (script_class->members_indices.has(first)) { GDScriptParser::ClassNode::Member member = script_class->members[script_class->members_indices[first]]; switch (member.type) { case GDScriptParser::ClassNode::Member::CLASS: - result.kind = GDScriptParser::DataType::CLASS; - result.class_type = member.m_class; + result = member.m_class->get_datatype(); found = true; break; case GDScriptParser::ClassNode::Member::ENUM: @@ -382,7 +414,8 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } if (!result.is_set()) { push_error(vformat(R"("%s" was not found in the current scope.)", first), p_type); - return GDScriptParser::DataType(); + result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type. + return result; } if (p_type->type_chain.size() > 1) { @@ -393,10 +426,12 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type 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(); + result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type. + return result; } 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(); + result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type. + return result; } } } else if (result.kind == GDScriptParser::DataType::NATIVE) { @@ -410,7 +445,8 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } } else { push_error(vformat(R"(Could not find nested type "%s" under base "%s".)", p_type->type_chain[1]->name, result.to_string()), p_type->type_chain[1]); - return GDScriptParser::DataType(); + result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type. + return result; } } @@ -444,12 +480,15 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas if (member.variable->datatype_specifier != nullptr) { datatype = resolve_datatype(member.variable->datatype_specifier); + datatype.is_meta_type = false; if (member.variable->initializer != nullptr) { if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) { push_error(vformat(R"(Value of type "%s" cannot be assigned to variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer); } else if (datatype.builtin_type == Variant::INT && member.variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { +#ifdef DEBUG_ENABLED parser->push_warning(member.variable->initializer, GDScriptWarning::NARROWING_CONVERSION); +#endif } if (member.variable->initializer->get_datatype().is_variant()) { // TODO: Warn unsafe assign. @@ -496,29 +535,34 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas 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); - } - GDScriptParser::DataType datatype = member.constant->get_datatype(); + if (member.constant->initializer) { + if (!member.constant->initializer->is_constant) { + push_error(R"(Initializer for a constant must be a constant expression.)", member.constant->initializer); + } - if (member.constant->datatype_specifier != nullptr) { - datatype = resolve_datatype(member.constant->datatype_specifier); + if (member.constant->datatype_specifier != nullptr) { + datatype = resolve_datatype(member.constant->datatype_specifier); + datatype.is_meta_type = false; - if (!is_type_compatible(datatype, member.constant->initializer->get_datatype(), true)) { - push_error(vformat(R"(Value of type "%s" cannot be initialized to constant of type "%s".)", member.constant->initializer->get_datatype().to_string(), datatype.to_string()), member.constant->initializer); - } else if (datatype.builtin_type == Variant::INT && member.constant->initializer->get_datatype().builtin_type == Variant::FLOAT) { - parser->push_warning(member.constant->initializer, GDScriptWarning::NARROWING_CONVERSION); + if (!is_type_compatible(datatype, member.constant->initializer->get_datatype(), true)) { + push_error(vformat(R"(Value of type "%s" cannot be initialized to constant of type "%s".)", member.constant->initializer->get_datatype().to_string(), datatype.to_string()), member.constant->initializer); + } else if (datatype.builtin_type == Variant::INT && member.constant->initializer->get_datatype().builtin_type == Variant::FLOAT) { +#ifdef DEBUG_ENABLED + parser->push_warning(member.constant->initializer, GDScriptWarning::NARROWING_CONVERSION); +#endif + } } } - datatype.is_constant = true; member.constant->set_datatype(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)); + GDScriptParser::DataType signal_type = resolve_datatype(member.signal->parameters[j]->datatype_specifier); + signal_type.is_meta_type = false; + member.signal->parameters[j]->set_datatype(signal_type); } // TODO: Make MethodInfo from signal. GDScriptParser::DataType signal_type; @@ -538,8 +582,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas enum_type.is_meta_type = true; enum_type.is_constant = true; - for (int i = 0; i < member.m_enum->values.size(); i++) { - enum_type.enum_values[member.m_enum->values[i].identifier->name] = member.m_enum->values[i].value; + for (int j = 0; j < member.m_enum->values.size(); j++) { + enum_type.enum_values[member.m_enum->values[j].identifier->name] = member.m_enum->values[j].value; } member.m_enum->set_datatype(enum_type); @@ -607,9 +651,11 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { if (member.type != GDScriptParser::ClassNode::Member::VARIABLE) { continue; } +#ifdef DEBUG_ENABLED if (member.variable->usages == 0 && String(member.variable->identifier->name).begins_with("_")) { parser->push_warning(member.variable->identifier, GDScriptWarning::UNUSED_PRIVATE_CLASS_VARIABLE, member.variable->identifier->name); } +#endif } } @@ -713,14 +759,26 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * for (int i = 0; i < p_function->parameters.size(); i++) { resolve_pararameter(p_function->parameters[i]); +#ifdef DEBUG_ENABLED if (p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_")) { parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, p_function->identifier->name, p_function->parameters[i]->identifier->name); } - is_shadowing(p_function->parameters[i]->identifier, "function parameter"); +#endif } - p_function->set_datatype(resolve_datatype(p_function->return_type)); + if (p_function->identifier->name == "_init") { + // Constructor. + GDScriptParser::DataType return_type = parser->current_class->get_datatype(); + return_type.is_meta_type = false; + p_function->set_datatype(return_type); + if (p_function->return_type) { + push_error("Constructor cannot have an explicit return type.", p_function->return_type); + } + } else { + GDScriptParser::DataType return_type = resolve_datatype(p_function->return_type); + p_function->set_datatype(return_type); + } parser->current_function = previous_function; } @@ -743,7 +801,7 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun return_type.type_source = GDScriptParser::DataType::INFERRED; p_function->set_datatype(p_function->body->get_datatype()); } else if (p_function->get_datatype().is_hard_type() && (p_function->get_datatype().kind != GDScriptParser::DataType::BUILTIN || p_function->get_datatype().builtin_type != Variant::NIL)) { - if (!p_function->body->has_return) { + if (!p_function->body->has_return && p_function->identifier->name != GDScriptLanguage::get_singleton()->strings._init) { push_error(R"(Not all code paths return a value.)", p_function); } } @@ -752,6 +810,9 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun } void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement) { + if (p_statement == nullptr) { + return; + } switch (p_statement->type) { case GDScriptParser::Node::IF: case GDScriptParser::Node::FOR: @@ -799,7 +860,7 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { // 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) { + if (p_for->list && 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); @@ -870,8 +931,11 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { resolve_suite(p_for->loop); p_for->set_datatype(p_for->loop->get_datatype()); - - is_shadowing(p_for->variable, R"("for" iterator variable)"); +#ifdef DEBUG_ENABLED + if (p_for->variable) { + is_shadowing(p_for->variable, R"("for" iterator variable)"); + } +#endif } void GDScriptAnalyzer::resolve_while(GDScriptParser::WhileNode *p_while) { @@ -896,25 +960,30 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable 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. Use explicit "Variant" type if this is intended.)", p_variable->identifier->name), p_variable->initializer); - } else if (type.builtin_type == Variant::NIL) { + } else if (type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL) { push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is "null".)", p_variable->identifier->name), p_variable->initializer); } } else { type.type_source = GDScriptParser::DataType::INFERRED; } +#ifdef DEBUG_ENABLED if (p_variable->initializer->type == GDScriptParser::Node::CALL && type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL) { parser->push_warning(p_variable->initializer, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_variable->initializer)->function_name); } +#endif } if (p_variable->datatype_specifier != nullptr) { type = resolve_datatype(p_variable->datatype_specifier); + type.is_meta_type = false; if (p_variable->initializer != nullptr) { if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true)) { push_error(vformat(R"(Value of type "%s" cannot be assigned to variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer); +#ifdef DEBUG_ENABLED } else if (type.builtin_type == Variant::INT && p_variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { parser->push_warning(p_variable->initializer, GDScriptWarning::NARROWING_CONVERSION); +#endif } if (p_variable->initializer->get_datatype().is_variant()) { // TODO: Warn unsafe assign. @@ -931,6 +1000,7 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable type.is_constant = false; p_variable->set_datatype(type); +#ifdef DEBUG_ENABLED if (p_variable->usages == 0 && !String(p_variable->identifier->name).begins_with("_")) { parser->push_warning(p_variable, GDScriptWarning::UNUSED_VARIABLE, p_variable->identifier->name); } else if (p_variable->assignments == 0) { @@ -938,6 +1008,7 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable } is_shadowing(p_variable->identifier, "variable"); +#endif } void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant) { @@ -951,16 +1022,21 @@ void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant type = p_constant->initializer->get_datatype(); +#ifdef DEBUG_ENABLED if (p_constant->initializer->type == GDScriptParser::Node::CALL && type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL) { parser->push_warning(p_constant->initializer, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_constant->initializer)->function_name); } +#endif if (p_constant->datatype_specifier != nullptr) { GDScriptParser::DataType explicit_type = resolve_datatype(p_constant->datatype_specifier); + explicit_type.is_meta_type = false; if (!is_type_compatible(explicit_type, type)) { push_error(vformat(R"(Assigned value for constant "%s" has type %s which is not compatible with defined type %s.)", p_constant->identifier->name, type.to_string(), explicit_type.to_string()), p_constant->initializer); +#ifdef DEBUG_ENABLED } else if (explicit_type.builtin_type == Variant::INT && type.builtin_type == Variant::FLOAT) { parser->push_warning(p_constant->initializer, GDScriptWarning::NARROWING_CONVERSION); +#endif } type = explicit_type; } else if (p_constant->infer_datatype) { @@ -973,11 +1049,13 @@ void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant type.is_constant = true; p_constant->set_datatype(type); +#ifdef DEBUG_ENABLED if (p_constant->usages == 0) { parser->push_warning(p_constant, GDScriptWarning::UNUSED_LOCAL_CONSTANT, p_constant->identifier->name); } is_shadowing(p_constant->identifier, "constant"); +#endif } void GDScriptAnalyzer::resolve_assert(GDScriptParser::AssertNode *p_assert) { @@ -988,6 +1066,7 @@ void GDScriptAnalyzer::resolve_assert(GDScriptParser::AssertNode *p_assert) { p_assert->set_datatype(p_assert->condition->get_datatype()); +#ifdef DEBUG_ENABLED if (p_assert->condition->is_constant) { if (p_assert->condition->reduced_value.booleanize()) { parser->push_warning(p_assert->condition, GDScriptWarning::ASSERT_ALWAYS_TRUE); @@ -995,6 +1074,7 @@ void GDScriptAnalyzer::resolve_assert(GDScriptParser::AssertNode *p_assert) { parser->push_warning(p_assert->condition, GDScriptWarning::ASSERT_ALWAYS_FALSE); } } +#endif } void GDScriptAnalyzer::resolve_match(GDScriptParser::MatchNode *p_match) { @@ -1018,19 +1098,27 @@ void GDScriptAnalyzer::resolve_match_branch(GDScriptParser::MatchBranchNode *p_m } void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_match_pattern, GDScriptParser::ExpressionNode *p_match_test) { + if (p_match_pattern == nullptr) { + return; + } + 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(); + if (p_match_pattern->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); + if (p_match_pattern->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(); } - result = p_match_pattern->expression->get_datatype(); break; case GDScriptParser::PatternNode::PT_BIND: if (p_match_test != nullptr) { @@ -1039,10 +1127,12 @@ void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_matc result.kind = GDScriptParser::DataType::VARIANT; } p_match_pattern->bind->set_datatype(result); +#ifdef DEBUG_ENABLED is_shadowing(p_match_pattern->bind, "pattern bind"); if (p_match_pattern->bind->usages == 0) { parser->push_warning(p_match_pattern->bind, GDScriptWarning::UNASSIGNED_VARIABLE, p_match_pattern->bind->name); } +#endif break; case GDScriptParser::PatternNode::PT_ARRAY: for (int i = 0; i < p_match_pattern->array.size(); i++) { @@ -1053,13 +1143,17 @@ void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_matc 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); + if (p_match_pattern->dictionary[i].key) { + 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); + if (p_match_pattern->dictionary[i].value_pattern) { + 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; @@ -1084,14 +1178,16 @@ void GDScriptAnalyzer::resolve_pararameter(GDScriptParser::ParameterNode *p_para if (p_parameter->datatype_specifier != nullptr) { resolve_datatype(p_parameter->datatype_specifier); + result = p_parameter->datatype_specifier->get_datatype(); + result.is_meta_type = false; if (p_parameter->default_value != nullptr) { - if (!is_type_compatible(p_parameter->datatype_specifier->get_datatype(), p_parameter->default_value->get_datatype())) { + if (!is_type_compatible(result, p_parameter->default_value->get_datatype())) { push_error(vformat(R"(Type of default value for parameter "%s" (%s) is not compatible with paremeter type (%s).)", p_parameter->identifier->name, p_parameter->default_value->get_datatype().to_string(), p_parameter->datatype_specifier->get_datatype().to_string()), p_parameter->default_value); + } else if (p_parameter->default_value->get_datatype().is_variant()) { + mark_node_unsafe(p_parameter); } } - - result = p_parameter->datatype_specifier->get_datatype(); } p_parameter->set_datatype(result); @@ -1112,6 +1208,7 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) { } GDScriptParser::DataType function_type = parser->current_function->get_datatype(); + function_type.is_meta_type = false; if (function_type.is_hard_type()) { if (!is_type_compatible(function_type, result)) { // Try other way. Okay but not safe. @@ -1121,10 +1218,12 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) { // TODO: Add warning. mark_node_unsafe(p_return); } +#ifdef DEBUG_ENABLED } else if (function_type.builtin_type == Variant::INT && result.builtin_type == Variant::FLOAT) { parser->push_warning(p_return, GDScriptWarning::NARROWING_CONVERSION); } else if (result.is_variant()) { mark_node_unsafe(p_return); +#endif } } @@ -1260,7 +1359,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig 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 (!is_type_compatible(p_assignment->assignee->get_datatype(), p_assignment->assigned_value->get_datatype(), true)) { 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 { @@ -1311,11 +1410,13 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype(); GDScriptParser::DataType assigned_type = p_assignment->assigned_value->get_datatype(); +#ifdef DEBUG_ENABLED if (p_assignment->assigned_value->type == GDScriptParser::Node::CALL && assigned_type.kind == GDScriptParser::DataType::BUILTIN && assigned_type.builtin_type == Variant::NIL) { parser->push_warning(p_assignment->assigned_value, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_assignment->assigned_value)->function_name); } else if (assignee_type.is_hard_type() && assignee_type.builtin_type == Variant::INT && assigned_type.builtin_type == Variant::FLOAT) { parser->push_warning(p_assignment->assigned_value, GDScriptWarning::NARROWING_CONVERSION); } +#endif } void GDScriptAnalyzer::reduce_await(GDScriptParser::AwaitNode *p_await) { @@ -1332,27 +1433,41 @@ void GDScriptAnalyzer::reduce_await(GDScriptParser::AwaitNode *p_await) { p_await->set_datatype(awaiting_type); +#ifdef DEBUG_ENABLED if (!awaiting_type.is_coroutine && awaiting_type.builtin_type != Variant::SIGNAL) { parser->push_warning(p_await, GDScriptWarning::REDUNDANT_AWAIT); } +#endif } void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_op) { reduce_expression(p_binary_op->left_operand); - if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST && p_binary_op->right_operand->type == GDScriptParser::Node::IDENTIFIER) { + if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST && p_binary_op->right_operand && p_binary_op->right_operand->type == GDScriptParser::Node::IDENTIFIER) { reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_binary_op->right_operand), true); } else { reduce_expression(p_binary_op->right_operand); } // TODO: Right operand must be a valid type with the `is` operator. Need to check here. - GDScriptParser::DataType left_type = p_binary_op->left_operand->get_datatype(); - GDScriptParser::DataType right_type = p_binary_op->right_operand->get_datatype(); + GDScriptParser::DataType left_type; + if (p_binary_op->left_operand) { + left_type = p_binary_op->left_operand->get_datatype(); + } + GDScriptParser::DataType right_type; + if (p_binary_op->right_operand) { + right_type = p_binary_op->right_operand->get_datatype(); + } + + if (!left_type.is_set() || !right_type.is_set()) { + return; + } +#ifdef DEBUG_ENABLED if (p_binary_op->variant_op == Variant::OP_DIVIDE && left_type.builtin_type == Variant::INT && right_type.builtin_type == Variant::INT) { parser->push_warning(p_binary_op, GDScriptWarning::INTEGER_DIVISION); } +#endif if (p_binary_op->left_operand->is_constant && p_binary_op->right_operand->is_constant) { p_binary_op->is_constant = true; @@ -1398,11 +1513,14 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o 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. - mark_node_unsafe(p_binary_op); + // Test reverse as well to consider for subtypes. + if (!is_type_compatible(p_binary_op->left_operand->get_datatype(), test_type, 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. + mark_node_unsafe(p_binary_op); + } } } @@ -1522,10 +1640,12 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype())) { types_match = false; break; +#ifdef DEBUG_ENABLED } else { if (par_type.builtin_type == Variant::INT && p_call->arguments[i]->get_datatype().builtin_type == Variant::FLOAT) { parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); } +#endif } } @@ -1649,18 +1769,22 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa // Can only be attribute. callee_id = static_cast<GDScriptParser::SubscriptNode *>(p_call->callee)->attribute; } - reduce_identifier_from_base(callee_id, &base_type); - GDScriptParser::DataType callee_type = callee_id->get_datatype(); - if (callee_type.is_set() && !callee_type.is_variant()) { - found = true; - if (callee_type.builtin_type == Variant::CALLABLE) { - push_error(vformat(R"*(Name "%s" is a Callable. You can call it with "%s.call()" instead.)*", p_call->function_name, p_call->function_name), p_call->callee); - } else { - push_error(vformat(R"*(Name "%s" called as a function but is a "%s".)*", p_call->function_name, callee_type.to_string()), p_call->callee); + if (callee_id) { + reduce_identifier_from_base(callee_id, &base_type); + GDScriptParser::DataType callee_type = callee_id->get_datatype(); + if (callee_type.is_set() && !callee_type.is_variant()) { + found = true; + if (callee_type.builtin_type == Variant::CALLABLE) { + push_error(vformat(R"*(Name "%s" is a Callable. You can call it with "%s.call()" instead.)*", p_call->function_name, p_call->function_name), p_call->callee); + } else { + push_error(vformat(R"*(Name "%s" called as a function but is a "%s".)*", p_call->function_name, callee_type.to_string()), p_call->callee); + } +#ifdef DEBUG_ENABLED + } else if (!is_self) { + parser->push_warning(p_call, GDScriptWarning::UNSAFE_METHOD_ACCESS, p_call->function_name, base_type.to_string()); + mark_node_unsafe(p_call); +#endif } - } else if (!is_self) { - parser->push_warning(p_call, GDScriptWarning::UNSAFE_METHOD_ACCESS, p_call->function_name, base_type.to_string()); - mark_node_unsafe(p_call); } } if (!found && is_self) { @@ -1685,33 +1809,32 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { return; } + cast_type.is_meta_type = false; // The casted value won't be a type name. 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); - } + 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); - } + 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); } +#ifdef DEBUG_ENABLED if (p_cast->operand->get_datatype().is_variant()) { parser->push_warning(p_cast, GDScriptWarning::UNSAFE_CAST, cast_type.to_string()); mark_node_unsafe(p_cast); } +#endif // TODO: Perform cast on constants. } @@ -1721,7 +1844,9 @@ void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dicti for (int i = 0; i < p_dictionary->elements.size(); i++) { const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; - reduce_expression(element.key); + if (p_dictionary->style == GDScriptParser::DictionaryNode::PYTHON_DICT) { + reduce_expression(element.key); + } reduce_expression(element.value); all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant; } @@ -1770,7 +1895,9 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str 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.script_path = ref->get_parser()->script_path; type.is_constant = true; + type.is_meta_type = true; return type; } @@ -1837,6 +1964,10 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod // TODO: Switch current class/function/suite here to avoid misrepresenting identifiers (in recursive reduce calls). while (base_class != nullptr) { + if (base_class->identifier && base_class->identifier->name == name) { + p_identifier->set_datatype(base_class->get_datatype()); + return; + } if (base_class->has_member(name)) { const GDScriptParser::ClassNode::Member &member = base_class->get_member(name); p_identifier->set_datatype(member.get_datatype()); @@ -2095,8 +2226,10 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri } else { if (base_type.kind == GDScriptParser::DataType::BUILTIN) { push_error(vformat(R"(Cannot find member "%s" in base "%s".)", p_subscript->attribute->name, base_type.to_string()), p_subscript->attribute); +#ifdef DEBUG_ENABLED } else { parser->push_warning(p_subscript, GDScriptWarning::UNSAFE_PROPERTY_ACCESS, p_subscript->attribute->name, base_type.to_string()); +#endif } result_type.kind = GDScriptParser::DataType::VARIANT; } @@ -2283,7 +2416,7 @@ void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternar GDScriptParser::DataType result; - if (p_ternary_op->condition->is_constant && p_ternary_op->true_expr->is_constant && p_ternary_op->false_expr->is_constant) { + if (p_ternary_op->condition && p_ternary_op->condition->is_constant && p_ternary_op->true_expr->is_constant && p_ternary_op->false_expr && 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; @@ -2292,8 +2425,18 @@ void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternar } } - GDScriptParser::DataType true_type = p_ternary_op->true_expr->get_datatype(); - GDScriptParser::DataType false_type = p_ternary_op->false_expr->get_datatype(); + GDScriptParser::DataType true_type; + if (p_ternary_op->true_expr) { + true_type = p_ternary_op->true_expr->get_datatype(); + } else { + true_type.kind = GDScriptParser::DataType::VARIANT; + } + GDScriptParser::DataType false_type; + if (p_ternary_op->false_expr) { + false_type = p_ternary_op->false_expr->get_datatype(); + } else { + false_type.kind = GDScriptParser::DataType::VARIANT; + } if (true_type.is_variant() || false_type.is_variant()) { result.kind = GDScriptParser::DataType::VARIANT; @@ -2304,8 +2447,9 @@ void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternar if (!is_type_compatible(false_type, true_type)) { result.type_source = GDScriptParser::DataType::UNDETECTED; result.kind = GDScriptParser::DataType::VARIANT; - +#ifdef DEBUG_ENABLED parser->push_warning(p_ternary_op, GDScriptWarning::INCOMPATIBLE_TERNARY); +#endif } } } @@ -2360,12 +2504,14 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va } if (scr.is_valid()) { result.script_type = scr; + result.script_path = scr->get_path(); 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; + result.script_path = ref->get_parser()->script_path; } else { result.kind = GDScriptParser::DataType::SCRIPT; } @@ -2410,6 +2556,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD r_static = false; r_vararg = false; r_default_arg_count = 0; + StringName function_name = p_function; if (p_base_type.kind == GDScriptParser::DataType::BUILTIN) { // Construct a base type to get methods. @@ -2430,23 +2577,29 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD return false; } + bool is_constructor = p_base_type.is_meta_type && p_function == "new"; + if (is_constructor) { + function_name = "_init"; + r_static = true; + } + 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) { + if (base_class->has_member(function_name)) { + if (base_class->get_member(function_name).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); + push_error(vformat(R"(Member "%s" is not a function.)", function_name), p_source); return false; } - found_function = base_class->get_member(p_function).function; + found_function = base_class->get_member(function_name).function; } base_class = base_class->base_type.class_type; } if (found_function != nullptr) { - r_static = found_function->is_static; + r_static = is_constructor || 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) { @@ -2459,12 +2612,10 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD 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); + MethodInfo info = base_script->get_method_info(function_name); if (!(info == MethodInfo())) { return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); @@ -2473,32 +2624,38 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD } // 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)) { + if (p_base_type.is_meta_type && !is_constructor && (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)) { + if (ClassDB::get_method_info(script_class, function_name, &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. +#ifdef DEBUG_ENABLED + if (base_native != StringName()) { + // Empty native class might happen in some Script implementations. // Just ignore it. - return false; + 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 -#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)); + if (is_constructor) { + // Native types always have a default constructor. + r_return_type = p_base_type; + r_return_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_return_type.is_meta_type = false; + return true; } -#endif StringName real_native = get_real_class_name(base_native); MethodInfo info; - if (ClassDB::get_method_info(real_native, p_function, &info)) { + if (ClassDB::get_method_info(real_native, function_name, &info)) { return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); } @@ -2558,15 +2715,18 @@ bool GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p p_call->arguments[i]); valid = false; } +#ifdef DEBUG_ENABLED } 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) { parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); } +#endif } } return valid; } +#ifdef DEBUG_ENABLED bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context) { const StringName &name = p_local->name; GDScriptParser::DataType base = parser->current_class->get_datatype(); @@ -2609,6 +2769,7 @@ bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con return false; } +#endif 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. @@ -2835,9 +2996,11 @@ void GDScriptAnalyzer::push_error(const String &p_message, const GDScriptParser: } void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) { +#ifdef DEBUG_ENABLED for (int i = p_node->start_line; i <= p_node->end_line; i++) { parser->unsafe_lines.insert(i); } +#endif } bool GDScriptAnalyzer::class_exists(const StringName &p_class) { @@ -2851,7 +3014,7 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) { ref = depended_parsers[p_path]; } else { Error err = OK; - ref = GDScriptCache::get_parser(p_path, GDScriptParserRef::EMPTY, err); + ref = GDScriptCache::get_parser(p_path, GDScriptParserRef::EMPTY, err, parser->script_path); depended_parsers[p_path] = ref; } diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index b21a003e47..85183d3272 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -96,13 +96,15 @@ class GDScriptAnalyzer { 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); - bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context); 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); +#ifdef DEBUG_ENABLED + bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context); +#endif public: Error resolve_inheritance(); diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index b7b2fef85b..583283ff46 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -109,9 +109,12 @@ void GDScriptCache::remove_script(const String &p_path) { singleton->full_gdscript_cache.erase(p_path); } -Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptParserRef::Status p_status, Error &r_error) { +Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptParserRef::Status p_status, Error &r_error, const String &p_owner) { MutexLock(singleton->lock); Ref<GDScriptParserRef> ref; + if (p_owner != String()) { + singleton->dependencies[p_owner].insert(p_path); + } if (singleton->parser_map.has(p_path)) { ref = singleton->parser_map[p_path]; } else { @@ -120,7 +123,7 @@ Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptP ref->parser = parser; ref->path = p_path; singleton->parser_map[p_path] = ref; - ref.unref(); + ref->unreference(); } r_error = ref->raise_status(p_status); @@ -164,7 +167,7 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const Stri Ref<GDScript> script; script.instance(); - script->set_path(p_path); + script->set_path(p_path, true); script->set_script_path(p_path); script->load_source_code(p_path); @@ -188,9 +191,15 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro } Ref<GDScript> script = get_shallow_script(p_path); + r_error = script->load_source_code(p_path); + + if (r_error) { + return script; + } + r_error = script->reload(); if (r_error) { - return Ref<GDScript>(); + return script; } singleton->full_gdscript_cache[p_path] = script; @@ -200,6 +209,11 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro } Error GDScriptCache::finish_compiling(const String &p_owner) { + // Mark this as compiled. + Ref<GDScript> script = get_shallow_script(p_owner); + singleton->full_gdscript_cache[p_owner] = script; + singleton->shallow_gdscript_cache.erase(p_owner); + Set<String> depends = singleton->dependencies[p_owner]; Error err = OK; @@ -213,6 +227,8 @@ Error GDScriptCache::finish_compiling(const String &p_owner) { } } + singleton->dependencies.erase(p_owner); + return err; } diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index 431ec7d863..770704d6eb 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -84,7 +84,7 @@ class GDScriptCache { static void remove_script(const String &p_path); public: - static Ref<GDScriptParserRef> get_parser(const String &p_path, GDScriptParserRef::Status status, Error &r_error); + static Ref<GDScriptParserRef> get_parser(const String &p_path, GDScriptParserRef::Status status, Error &r_error, const String &p_owner = String()); static String get_source_code(const String &p_path); static Ref<GDScript> get_shallow_script(const String &p_path, const String &p_owner = String()); static Ref<GDScript> get_full_script(const String &p_path, Error &r_error, const String &p_owner = String()); diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index da19ce5000..3d37c7f803 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -31,6 +31,7 @@ #include "gdscript_compiler.h" #include "gdscript.h" +#include "gdscript_cache.h" bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringName &p_name) { if (codegen.function_node && codegen.function_node->is_static) { @@ -114,7 +115,7 @@ bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptP } GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const { - if (!p_datatype.is_set()) { + if (!p_datatype.is_set() || !p_datatype.is_hard_type()) { return GDScriptDataType(); } @@ -122,6 +123,9 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D result.has_type = true; switch (p_datatype.kind) { + case GDScriptParser::DataType::VARIANT: { + result.has_type = false; + } break; case GDScriptParser::DataType::BUILTIN: { result.kind = GDScriptDataType::BUILTIN; result.builtin_type = p_datatype.builtin_type; @@ -139,38 +143,48 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D // Locate class by constructing the path to it and following that path GDScriptParser::ClassNode *class_type = p_datatype.class_type; if (class_type) { - List<StringName> names; - while (class_type->outer) { - names.push_back(class_type->identifier->name); - class_type = class_type->outer; - } - - Ref<GDScript> script = Ref<GDScript>(main_script); - while (names.back()) { - if (!script->subclasses.has(names.back()->get())) { - ERR_PRINT("Parser bug: Cannot locate datatype class."); - result.has_type = false; - return GDScriptDataType(); + if (class_type->fqcn.begins_with(main_script->path) || (!main_script->name.empty() && class_type->fqcn.begins_with(main_script->name))) { + // Local class. + List<StringName> names; + while (class_type->outer) { + names.push_back(class_type->identifier->name); + class_type = class_type->outer; + } + + Ref<GDScript> script = Ref<GDScript>(main_script); + while (names.back()) { + if (!script->subclasses.has(names.back()->get())) { + ERR_PRINT("Parser bug: Cannot locate datatype class."); + result.has_type = false; + return GDScriptDataType(); + } + script = script->subclasses[names.back()->get()]; + names.pop_back(); } - script = script->subclasses[names.back()->get()]; - names.pop_back(); + result.kind = GDScriptDataType::GDSCRIPT; + result.script_type = script; + result.native_type = script->get_instance_base_type(); + } else { + result.kind = GDScriptDataType::GDSCRIPT; + result.script_type = GDScriptCache::get_shallow_script(p_datatype.script_path, main_script->path); + result.native_type = p_datatype.native_type; } - result.kind = GDScriptDataType::GDSCRIPT; - result.script_type = script; - result.native_type = script->get_instance_base_type(); - } else { - result.kind = GDScriptDataType::GDSCRIPT; - result.script_type = p_datatype.script_type; - result.native_type = result.script_type->get_instance_base_type(); } - } break; + case GDScriptParser::DataType::ENUM_VALUE: + result.has_type = true; + result.kind = GDScriptDataType::BUILTIN; + result.builtin_type = Variant::INT; + break; + case GDScriptParser::DataType::ENUM: + result.has_type = true; + result.kind = GDScriptDataType::BUILTIN; + result.builtin_type = Variant::DICTIONARY; + break; case GDScriptParser::DataType::UNRESOLVED: { ERR_PRINT("Parser bug: converting unresolved type."); return GDScriptDataType(); } - default: - break; // FIXME } return result; @@ -234,6 +248,66 @@ int GDScriptCompiler::_parse_assign_right_expression(CodeGen &codegen, const GDS return dst_addr; } +bool GDScriptCompiler::_generate_typed_assign(CodeGen &codegen, int p_src_address, int p_dst_address, const GDScriptDataType &p_datatype, const GDScriptParser::DataType &p_value_type) { + if (p_datatype.has_type && p_value_type.is_variant()) { + // Typed assignment + switch (p_datatype.kind) { + case GDScriptDataType::BUILTIN: { + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator + codegen.opcodes.push_back(p_datatype.builtin_type); // variable type + codegen.opcodes.push_back(p_dst_address); // argument 1 + codegen.opcodes.push_back(p_src_address); // argument 2 + } break; + case GDScriptDataType::NATIVE: { + int class_idx; + if (GDScriptLanguage::get_singleton()->get_global_map().has(p_datatype.native_type)) { + class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_datatype.native_type]; + class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) + } else { + // _set_error("Invalid native class type '" + String(p_datatype.native_type) + "'.", on->arguments[0]); + return false; + } + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE); // perform operator + codegen.opcodes.push_back(class_idx); // variable type + codegen.opcodes.push_back(p_dst_address); // argument 1 + codegen.opcodes.push_back(p_src_address); // argument 2 + } break; + case GDScriptDataType::SCRIPT: + case GDScriptDataType::GDSCRIPT: { + Variant script = p_datatype.script_type; + int idx = codegen.get_constant_pos(script); //make it a local constant (faster access) + + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); // perform operator + codegen.opcodes.push_back(idx); // variable type + codegen.opcodes.push_back(p_dst_address); // argument 1 + codegen.opcodes.push_back(p_src_address); // argument 2 + } break; + default: { + ERR_PRINT("Compiler bug: unresolved assign."); + + // Shouldn't get here, but fail-safe to a regular assignment + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator + codegen.opcodes.push_back(p_dst_address); // argument 1 + codegen.opcodes.push_back(p_src_address); // argument 2 (unary only takes one parameter) + } + } + } else { + if (p_datatype.kind == GDScriptDataType::BUILTIN && p_value_type.kind == GDScriptParser::DataType::BUILTIN && p_datatype.builtin_type != p_value_type.builtin_type) { + // Need conversion. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator + codegen.opcodes.push_back(p_datatype.builtin_type); // variable type + codegen.opcodes.push_back(p_dst_address); // argument 1 + codegen.opcodes.push_back(p_src_address); // argument 2 + } else { + // Either untyped assignment or already type-checked by the parser + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator + codegen.opcodes.push_back(p_dst_address); // argument 1 + codegen.opcodes.push_back(p_src_address); // argument 2 (unary only takes one parameter) + } + } + return true; +} + 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); @@ -368,15 +442,16 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: class_node = class_node->outer; } - if (class_node->identifier && class_node->identifier->name == identifier) { - _set_error("Using own name in class file is not allowed (creates a cyclic reference)", p_expression); - return -1; - } + RES res; - RES res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier)); - if (res.is_null()) { - _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); - return -1; + if (class_node->identifier && class_node->identifier->name == identifier) { + res = Ref<GDScript>(main_script); + } else { + res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier)); + if (res.is_null()) { + _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); + return -1; + } } Variant key = res; @@ -534,8 +609,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: codegen.alloc_stack(slevel); } - // FIXME: Allow actual cast. - GDScriptDataType cast_type; // = _gdtype_from_datatype(cn->cast_type); + GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type->get_datatype()); switch (cast_type.kind) { case GDScriptDataType::BUILTIN: { @@ -685,8 +759,6 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: arguments.push_back(ret); arguments.push_back(codegen.get_name_map_pos(subscript->attribute->name)); } else { - // TODO: Validate this at parse time. - // TODO: Though might not be the case if callables are a thing. _set_error("Cannot call something that isn't a function.", call->callee); return -1; } @@ -1329,55 +1401,6 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype()); - if (assign_type.has_type && !assignment->assigned_value->get_datatype().is_variant()) { - // Typed assignment - switch (assign_type.kind) { - case GDScriptDataType::BUILTIN: { - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator - codegen.opcodes.push_back(assign_type.builtin_type); // variable type - codegen.opcodes.push_back(dst_address_a); // argument 1 - codegen.opcodes.push_back(src_address_b); // argument 2 - } break; - case GDScriptDataType::NATIVE: { - int class_idx; - if (GDScriptLanguage::get_singleton()->get_global_map().has(assign_type.native_type)) { - class_idx = GDScriptLanguage::get_singleton()->get_global_map()[assign_type.native_type]; - class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) - } else { - // _set_error("Invalid native class type '" + String(assign_type.native_type) + "'.", on->arguments[0]); - return -1; - } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE); // perform operator - codegen.opcodes.push_back(class_idx); // variable type - codegen.opcodes.push_back(dst_address_a); // argument 1 - codegen.opcodes.push_back(src_address_b); // argument 2 - } break; - case GDScriptDataType::SCRIPT: - case GDScriptDataType::GDSCRIPT: { - Variant script = assign_type.script_type; - int idx = codegen.get_constant_pos(script); //make it a local constant (faster access) - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); // perform operator - codegen.opcodes.push_back(idx); // variable type - codegen.opcodes.push_back(dst_address_a); // argument 1 - codegen.opcodes.push_back(src_address_b); // argument 2 - } break; - default: { - ERR_PRINT("Compiler bug: unresolved assign."); - - // Shouldn't get here, but fail-safe to a regular assignment - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator - codegen.opcodes.push_back(dst_address_a); // argument 1 - codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) - } - } - } else { - // Either untyped assignment or already type-checked by the parser - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator - codegen.opcodes.push_back(dst_address_a); // argument 1 - codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) - } - if (has_setter && !is_in_setter) { // Call setter. codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL); @@ -1387,6 +1410,8 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: codegen.opcodes.push_back(dst_address_a); // Argument. codegen.opcodes.push_back(dst_address_a); // Result address (won't be used here). codegen.alloc_call(1); + } else if (!_generate_typed_assign(codegen, src_address_b, dst_address_a, assign_type, assignment->assigned_value->get_datatype())) { + return -1; } return dst_address_a; //if anything, returns wathever was assigned or correct stack position @@ -1662,7 +1687,7 @@ Error GDScriptCompiler::_parse_match_pattern(CodeGen &codegen, const GDScriptPar // Evaluate element by element. for (int i = 0; i < p_pattern->dictionary.size(); i++) { const GDScriptParser::PatternNode::Pair &element = p_pattern->dictionary[i]; - if (element.value_pattern->pattern_type == GDScriptParser::PatternNode::PT_REST) { + if (element.value_pattern && element.value_pattern->pattern_type == GDScriptParser::PatternNode::PT_REST) { // Ignore rest pattern. continue; } @@ -1698,28 +1723,30 @@ Error GDScriptCompiler::_parse_match_pattern(CodeGen &codegen, const GDScriptPar r_patch_addresses.push_back(codegen.opcodes.size()); codegen.opcodes.push_back(0); // Will be replaced. - // Get actual value from user dictionary. - int value_addr = stlevel++; - codegen.alloc_stack(stlevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET); - codegen.opcodes.push_back(p_value_addr); // Source. - codegen.opcodes.push_back(pattern_key_addr); // Index. - codegen.opcodes.push_back(value_addr); // Destination. - - // Also get type of value. - int value_type_addr = stlevel++; - value_type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - codegen.alloc_stack(stlevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); - codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF); - codegen.opcodes.push_back(1); // One argument. - codegen.opcodes.push_back(value_addr); // Argument is the value we want to test. - codegen.opcodes.push_back(value_type_addr); // Address to result. - - // Try the pattern inside the value. - Error err = _parse_match_pattern(codegen, element.value_pattern, stlevel, value_addr, value_type_addr, r_bound_variables, r_patch_addresses, element_block_patches); - if (err != OK) { - return err; + if (element.value_pattern != nullptr) { + // Get actual value from user dictionary. + int value_addr = stlevel++; + codegen.alloc_stack(stlevel); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET); + codegen.opcodes.push_back(p_value_addr); // Source. + codegen.opcodes.push_back(pattern_key_addr); // Index. + codegen.opcodes.push_back(value_addr); // Destination. + + // Also get type of value. + int value_type_addr = stlevel++; + value_type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; + codegen.alloc_stack(stlevel); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); + codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF); + codegen.opcodes.push_back(1); // One argument. + codegen.opcodes.push_back(value_addr); // Argument is the value we want to test. + codegen.opcodes.push_back(value_type_addr); // Address to result. + + // Try the pattern inside the value. + Error err = _parse_match_pattern(codegen, element.value_pattern, stlevel, value_addr, value_type_addr, r_bound_variables, r_patch_addresses, element_block_patches); + if (err != OK) { + return err; + } } // Patch jumps to block to try the next element. @@ -2055,20 +2082,19 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui if (src_address < 0) { return ERR_PARSE_ERROR; } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); - codegen.opcodes.push_back(dst_address); - codegen.opcodes.push_back(src_address); + if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(lv->get_datatype()), lv->initializer->get_datatype())) { + return ERR_PARSE_ERROR; + } } } break; case GDScriptParser::Node::CONSTANT: { // Local constants. const GDScriptParser::ConstantNode *lc = static_cast<const GDScriptParser::ConstantNode *>(s); - // FIXME: Need to properly reduce expressions to avoid limiting this so much. - if (lc->initializer->type != GDScriptParser::Node::LITERAL) { - _set_error("Local constant must have a literal as initializer.", lc->initializer); + if (!lc->initializer->is_constant) { + _set_error("Local constant must have a constant value as initializer.", lc->initializer); return ERR_PARSE_ERROR; } - codegen.local_named_constants[lc->identifier->name] = codegen.get_constant_pos(static_cast<const GDScriptParser::LiteralNode *>(lc->initializer)->value); + codegen.local_named_constants[lc->identifier->name] = codegen.get_constant_pos(lc->initializer->reduced_value); } break; case GDScriptParser::Node::PASS: // Nothing to do. @@ -2158,9 +2184,9 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser int dst_address = codegen.script->member_indices[field->identifier->name].index; dst_address |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS; - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); - codegen.opcodes.push_back(dst_address); - codegen.opcodes.push_back(src_address); + if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(field->get_datatype()), field->initializer->get_datatype())) { + return ERR_PARSE_ERROR; + } } } } @@ -2188,9 +2214,9 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser int dst_address = codegen.script->member_indices[field->identifier->name].index; dst_address |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS; - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); - codegen.opcodes.push_back(dst_address); - codegen.opcodes.push_back(src_address); + if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(field->get_datatype()), field->initializer->get_datatype())) { + return ERR_PARSE_ERROR; + } } } } @@ -2209,9 +2235,10 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser if (src_addr < 0) { return ERR_PARSE_ERROR; } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); - codegen.opcodes.push_back(codegen.stack_identifiers[p_func->parameters[i]->identifier->name] | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS)); - codegen.opcodes.push_back(src_addr); + int dst_addr = codegen.stack_identifiers[p_func->parameters[i]->identifier->name] | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS); + if (!_generate_typed_assign(codegen, src_addr, dst_addr, _gdtype_from_datatype(p_func->parameters[i]->get_datatype()), p_func->parameters[i]->default_value->get_datatype())) { + return ERR_PARSE_ERROR; + } defarg_addr.push_back(codegen.opcodes.size()); } defarg_addr.invert(); @@ -2247,12 +2274,11 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser if (p_func) { gdfunc->_static = p_func->is_static; gdfunc->rpc_mode = p_func->rpc_mode; - // FIXME: Add actual parameters types; gdfunc->argument_types.resize(p_func->parameters.size()); for (int i = 0; i < p_func->parameters.size(); i++) { - gdfunc->argument_types.write[i] = GDScriptDataType(); //_gdtype_from_datatype(p_func->argument_types[i]); + gdfunc->argument_types.write[i] = _gdtype_from_datatype(p_func->parameters[i]->get_datatype()); } - gdfunc->return_type = GDScriptDataType(); //_gdtype_from_datatype(p_func->return_type); + gdfunc->return_type = _gdtype_from_datatype(p_func->get_datatype()); } else { gdfunc->_static = false; gdfunc->rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; @@ -2432,7 +2458,7 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP gdfunc->_static = false; gdfunc->rpc_mode = p_variable->rpc_mode; gdfunc->argument_types.resize(p_is_setter ? 1 : 0); - gdfunc->return_type = GDScriptDataType(); // TODO + gdfunc->return_type = _gdtype_from_datatype(p_variable->get_datatype()); #ifdef TOOLS_ENABLED gdfunc->arg_names = argnames; #endif @@ -2581,7 +2607,6 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar Ref<GDScript> base = base_type.script_type; p_script->base = base; 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.class_type != nullptr) { if (!parsed_classes.has(p_script->_base)) { @@ -2596,6 +2621,8 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar } } } + + p_script->member_indices = base->member_indices; } break; default: { _set_error("Parser bug: invalid inheritance.", p_class); @@ -2633,8 +2660,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar break; } minfo.rpc_mode = variable->rpc_mode; - // FIXME: Types. - // minfo.data_type = _gdtype_from_datatype(p_class->variables[i].data_type); + minfo.data_type = _gdtype_from_datatype(variable->get_datatype()); PropertyInfo prop_info = minfo.data_type; prop_info.name = name; @@ -2965,7 +2991,7 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri return err; } - return OK; + return GDScriptCache::finish_compiling(p_script->get_path()); } String GDScriptCompiler::get_error() const { diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 490768ea96..e8601f69c7 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -146,6 +146,7 @@ class GDScriptCompiler { bool _create_unary_operator(CodeGen &codegen, const GDScriptParser::UnaryOpNode *on, Variant::Operator op, int p_stack_level); bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0); bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0); + bool _generate_typed_assign(CodeGen &codegen, int p_src_address, int p_dst_address, const GDScriptDataType &p_datatype, const GDScriptParser::DataType &p_value_type); GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index e1ca1c9f48..239015060e 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -171,6 +171,7 @@ bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int & } } +#ifdef DEBUG_ENABLED if (r_safe_lines) { const Set<int> &unsafe_lines = parser.get_unsafe_lines(); for (int i = 1; i <= parser.get_last_line_number(); i++) { @@ -179,6 +180,7 @@ bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int & } } } +#endif return true; } @@ -1994,8 +1996,8 @@ static void _find_last_return_in_block(GDScriptParser::CompletionContext &p_cont } break; case GDScriptParser::Node::MATCH: { const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(p_context.current_suite->statements[i]); - for (int i = 0; i < match->branches.size(); i++) { - c.current_suite = match->branches[i]->block; + for (int j = 0; j < match->branches.size(); j++) { + c.current_suite = match->branches[j]->block; _find_last_return_in_block(c, r_last_return_line, r_last_returned_value); } } break; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index d25a8688be..af07457750 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -151,24 +151,6 @@ GDScriptParser::~GDScriptParser() { clear(); } -template <class T> -T *GDScriptParser::alloc_node() { - T *node = memnew(T); - - node->next = list; - list = node; - - // TODO: Properly set positions for all nodes. - node->start_line = previous.start_line; - node->end_line = previous.end_line; - node->start_column = previous.start_column; - node->end_column = previous.end_column; - node->leftmost_column = previous.leftmost_column; - node->rightmost_column = previous.rightmost_column; - - return node; -} - void GDScriptParser::clear() { while (list != nullptr) { Node *element = list; @@ -196,6 +178,7 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) { } } +#ifdef DEBUG_ENABLED void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1, const String &p_symbol2, const String &p_symbol3, const String &p_symbol4) { Vector<String> symbols; if (!p_symbol1.empty()) { @@ -250,6 +233,7 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_ warnings.push_front(warning); } } +#endif void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument, bool p_force) { if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) { @@ -542,6 +526,7 @@ void GDScriptParser::parse_program() { push_error(R"("extends" can only be used once.)"); } else { parse_extends(); + end_statement("superclass"); } break; default: @@ -589,7 +574,7 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() { n_class->identifier = parse_identifier(); } - if (check(GDScriptTokenizer::Token::EXTENDS)) { + if (match(GDScriptTokenizer::Token::EXTENDS)) { parse_extends(); } @@ -628,6 +613,7 @@ void GDScriptParser::parse_class_name() { if (match(GDScriptTokenizer::Token::EXTENDS)) { // Allow extends on the same line. parse_extends(); + end_statement("superclass"); } else { end_statement("class_name statement"); } @@ -664,8 +650,6 @@ void GDScriptParser::parse_extends() { } current_class->extends.push_back(previous.literal); } - - end_statement("superclass"); } template <class T> @@ -692,7 +676,7 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)() // Enums may be unnamed. // TODO: Consider names in outer scope too, for constants and classes (and static functions?) if (current_class->members_indices.has(member->identifier->name)) { - push_error(vformat(R"(%s "%s" has the same name as a previously declared %s.)", p_member_kind.capitalize(), member->identifier->name, current_class->get_member(member->identifier->name).get_type_name())); + push_error(vformat(R"(%s "%s" has the same name as a previously declared %s.)", p_member_kind.capitalize(), member->identifier->name, current_class->get_member(member->identifier->name).get_type_name()), member->identifier); } else { current_class->add_member(member); } @@ -731,6 +715,7 @@ void GDScriptParser::parse_class_body() { break; } case GDScriptTokenizer::Token::PASS: + advance(); end_statement(R"("pass")"); break; case GDScriptTokenizer::Token::DEDENT: @@ -1046,7 +1031,6 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifer for enum key.)")) { EnumNode::Value item; item.identifier = parse_identifier(); - bool found = false; if (!named) { // TODO: Abstract this recursive member check. @@ -1054,7 +1038,6 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { while (parent != nullptr) { if (parent->members_indices.has(item.identifier->name)) { push_error(vformat(R"(Name "%s" is already used as a class %s.)", item.identifier->name, parent->get_member(item.identifier->name).get_type_name())); - found = true; break; } parent = parent->outer; @@ -1193,10 +1176,10 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali push_completion_call(annotation); make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true); if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) { - int argument = 0; + int argument_index = 0; do { - make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument, true); - set_last_completion_call_arg(argument++, true); + make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true); + set_last_completion_call_arg(argument_index++); ExpressionNode *argument = parse_expression(false); if (argument == nullptr) { valid = false; @@ -1313,7 +1296,9 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, GDScriptParser::Node *GDScriptParser::parse_statement() { Node *result = nullptr; +#ifdef DEBUG_ENABLED bool unreachable = current_suite->has_return && !current_suite->has_unreachable_code; +#endif switch (current.type) { case GDScriptTokenizer::Token::PASS: @@ -1357,6 +1342,9 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { advance(); ReturnNode *n_return = alloc_node<ReturnNode>(); if (!is_statement_end()) { + if (current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) { + push_error(R"(Constructor cannot return a value.)"); + } n_return->return_value = parse_expression(false); } result = n_return; @@ -1392,6 +1380,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { end_statement("expression"); result = expression; +#ifdef DEBUG_ENABLED if (expression != nullptr) { switch (expression->type) { case Node::CALL: @@ -1403,14 +1392,17 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { push_warning(expression, GDScriptWarning::STANDALONE_EXPRESSION); } } +#endif break; } } +#ifdef DEBUG_ENABLED if (unreachable) { current_suite->has_unreachable_code = true; push_warning(result, GDScriptWarning::UNREACHABLE_CODE, current_function->identifier->name); } +#endif if (panic_mode) { synchronize(); @@ -1488,7 +1480,9 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { can_continue = true; SuiteNode *suite = alloc_node<SuiteNode>(); - suite->add_local(SuiteNode::Local(n_for->variable)); + if (n_for->variable) { + suite->add_local(SuiteNode::Local(n_for->variable)); + } suite->parent_for = n_for; n_for->loop = parse_suite(R"("for" block)", suite); @@ -1553,11 +1547,12 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() { return match; } +#ifdef DEBUG_ENABLED bool all_have_return = true; bool have_wildcard = false; bool wildcard_has_return = false; bool have_wildcard_without_continue = false; - bool have_unreachable_pattern = false; +#endif while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) { MatchBranchNode *branch = parse_match_branch(); @@ -1565,7 +1560,8 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() { continue; } - if (have_wildcard_without_continue && !have_unreachable_pattern) { +#ifdef DEBUG_ENABLED + if (have_wildcard_without_continue) { push_warning(branch->patterns[0], GDScriptWarning::UNREACHABLE_PATTERN); } @@ -1581,14 +1577,17 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() { if (!branch->block->has_return) { all_have_return = false; } +#endif match->branches.push_back(branch); } consume(GDScriptTokenizer::Token::DEDENT, R"(Expected an indented block after "match" statement.)"); +#ifdef DEBUG_ENABLED if (wildcard_has_return || (all_have_return && have_wildcard)) { current_suite->has_return = true; } +#endif return match; } @@ -1740,7 +1739,7 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ if (key == nullptr) { push_error(R"(Expected expression as key for dictionary pattern.)"); } - if (consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after dictionary pattern key)")) { + if (match(GDScriptTokenizer::Token::COLON)) { // Value pattern. PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern); if (sub_pattern == nullptr) { @@ -1753,6 +1752,9 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ } else { pattern->dictionary.push_back({ key, sub_pattern }); } + } else { + // Key match only. + pattern->dictionary.push_back({ key, nullptr }); } } } while (match(GDScriptTokenizer::Token::COMMA)); @@ -1824,6 +1826,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr break; // Nothing to do. } + // Completion can appear whenever an expression is expected. + make_completion_context(COMPLETION_IDENTIFIER, nullptr); + GDScriptTokenizer::Token token = advance(); ParseFunction prefix_rule = get_rule(token.type)->prefix; @@ -1871,8 +1876,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode IdentifierNode *identifier = alloc_node<IdentifierNode>(); identifier->name = previous.get_identifier(); - make_completion_context(COMPLETION_IDENTIFIER, identifier); - if (current_suite != nullptr && current_suite->has_local(identifier->name)) { const SuiteNode::Local &declaration = current_suite->get_local(identifier->name); switch (declaration.type) { @@ -1925,7 +1928,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_ } GDScriptParser::ExpressionNode *GDScriptParser::parse_self(ExpressionNode *p_previous_operand, bool p_can_assign) { - // FIXME: Don't allow "self" in a static context. + if (!current_function || current_function->is_static) { + push_error(R"(Cannot use "self" outside a non-static function.)"); + } SelfNode *self = alloc_node<SelfNode>(); self->current_class = current_class; return self; @@ -2115,15 +2120,19 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode return parse_expression(false); // Return the following expression. } +#ifdef DEBUG_ENABLED VariableNode *source_variable = nullptr; +#endif switch (p_previous_operand->type) { case Node::IDENTIFIER: { +#ifdef DEBUG_ENABLED // Get source to store assignment count. // Also remove one usage since assignment isn't usage. IdentifierNode *id = static_cast<IdentifierNode *>(p_previous_operand); switch (id->source) { case IdentifierNode::LOCAL_VARIABLE: + source_variable = id->variable_source; id->variable_source->usages--; break; @@ -2140,8 +2149,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode default: break; } - break; - } +#endif + } break; case Node::SUBSCRIPT: // Okay. break; @@ -2152,11 +2161,15 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode AssignmentNode *assignment = alloc_node<AssignmentNode>(); make_completion_context(COMPLETION_ASSIGN, assignment); +#ifdef DEBUG_ENABLED bool has_operator = true; +#endif switch (previous.type) { case GDScriptTokenizer::Token::EQUAL: assignment->operation = AssignmentNode::OP_NONE; +#ifdef DEBUG_ENABLED has_operator = false; +#endif break; case GDScriptTokenizer::Token::PLUS_EQUAL: assignment->operation = AssignmentNode::OP_ADDITION; @@ -2194,9 +2207,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode assignment->assignee = p_previous_operand; assignment->assigned_value = parse_expression(false); +#ifdef DEBUG_ENABLED if (has_operator && source_variable != nullptr && source_variable->assignments == 0) { push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name); } +#endif return assignment; } @@ -2391,7 +2406,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre call->function_name = current_function->identifier->name; } else { consume(GDScriptTokenizer::Token::PERIOD, R"(Expected "." or "(" after "super".)"); - make_completion_context(COMPLETION_SUPER_METHOD, call); + make_completion_context(COMPLETION_SUPER_METHOD, call, true); if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after ".".)")) { pop_multiline(); return nullptr; @@ -2406,10 +2421,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre if (call->callee->type == Node::IDENTIFIER) { call->function_name = static_cast<IdentifierNode *>(call->callee)->name; + make_completion_context(COMPLETION_METHOD, call->callee); } else if (call->callee->type == Node::SUBSCRIPT) { SubscriptNode *attribute = static_cast<SubscriptNode *>(call->callee); if (attribute->is_attribute) { - call->function_name = attribute->attribute->name; + if (attribute->attribute) { + call->function_name = attribute->attribute->name; + } + make_completion_context(COMPLETION_ATTRIBUTE_METHOD, call->callee); } else { // TODO: The analyzer can see if this is actually a Callable and give better error message. push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*"); @@ -2422,10 +2441,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { // Arguments. push_completion_call(call); - make_completion_context(COMPLETION_CALL_ARGUMENTS, call, 0); - int argument = 0; + make_completion_context(COMPLETION_CALL_ARGUMENTS, call, 0, true); + int argument_index = 0; do { - make_completion_context(COMPLETION_CALL_ARGUMENTS, call, argument++); + make_completion_context(COMPLETION_CALL_ARGUMENTS, call, argument_index++, true); if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { // Allow for trailing comma. break; @@ -2543,8 +2562,8 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { if (!match(GDScriptTokenizer::Token::IDENTIFIER)) { if (match(GDScriptTokenizer::Token::VOID)) { if (p_allow_void) { - TypeNode *type = alloc_node<TypeNode>(); - return type; + TypeNode *void_type = alloc_node<TypeNode>(); + return void_type; } else { push_error(R"("void" is only allowed for a function return type.)"); } @@ -2649,6 +2668,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty { nullptr, nullptr, PREC_NONE }, // SIGNAL, { nullptr, nullptr, PREC_NONE }, // STATIC, { &GDScriptParser::parse_call, nullptr, PREC_NONE }, // SUPER, + { nullptr, nullptr, PREC_NONE }, // TRAIT, { nullptr, nullptr, PREC_NONE }, // VAR, { nullptr, nullptr, PREC_NONE }, // VOID, { nullptr, nullptr, PREC_NONE }, // YIELD, @@ -2961,7 +2981,7 @@ String GDScriptParser::DataType::to_string() const { if (!name.empty()) { return name; } - name = script_type->get_path(); + name = script_path; if (!name.empty()) { return name; } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 4c695d62f7..a741ae0cc7 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -42,12 +42,13 @@ #include "core/ustring.h" #include "core/variant.h" #include "core/vector.h" +#include "gdscript_cache.h" #include "gdscript_functions.h" #include "gdscript_tokenizer.h" -#include "gdscript_warning.h" #ifdef DEBUG_ENABLED #include "core/string_builder.h" +#include "gdscript_warning.h" #endif // DEBUG_ENABLED class GDScriptParser { @@ -124,6 +125,7 @@ public: StringName native_type; StringName enum_type; // Enum name or the value name in an enum. Ref<Script> script_type; + String script_path; ClassNode *class_type = nullptr; MethodInfo method_info; // For callable/signals. @@ -131,7 +133,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_variant() const { return kind == VARIANT || kind == UNRESOLVED; } _FORCE_INLINE_ bool is_hard_type() const { return type_source > INFERRED; } String to_string() const; @@ -1019,12 +1021,14 @@ public: COMPLETION_ANNOTATION_ARGUMENTS, // Annotation arguments hint. COMPLETION_ASSIGN, // Assignment based on type (e.g. enum values). COMPLETION_ATTRIBUTE, // After id.| to look for members. + COMPLETION_ATTRIBUTE_METHOD, // After id.| to look for methods. COMPLETION_BUILT_IN_TYPE_CONSTANT, // Constants inside a built-in type (e.g. Color.blue). COMPLETION_CALL_ARGUMENTS, // Complete with nodes, input actions, enum values (or usual expressions). // TODO: COMPLETION_DECLARATION, // Potential declaration (var, const, func). COMPLETION_GET_NODE, // Get node with $ notation. COMPLETION_IDENTIFIER, // List available identifiers in scope. COMPLETION_INHERIT_TYPE, // Type after extends. Exclude non-viable types (built-ins, enums, void). Includes subtypes using the argument index. + COMPLETION_METHOD, // List available methods in scope. COMPLETION_OVERRIDE_METHOD, // Override implementation, also for native virtuals. COMPLETION_PROPERTY_DECLARATION, // Property declaration (get, set). COMPLETION_PROPERTY_DECLARATION_OR_TYPE, // Property declaration (get, set) or a type hint. @@ -1047,6 +1051,7 @@ public: Variant::Type builtin_type = Variant::VARIANT_MAX; Node *node = nullptr; Object *base = nullptr; + List<Ref<GDScriptParserRef>> dependent_parsers; }; struct CompletionCall { @@ -1069,9 +1074,11 @@ private: ClassNode *head = nullptr; Node *list = nullptr; List<ParserError> errors; +#ifdef DEBUG_ENABLED List<GDScriptWarning> warnings; Set<String> ignored_warnings; Set<int> unsafe_lines; +#endif GDScriptTokenizer tokenizer; GDScriptTokenizer::Token previous; @@ -1142,11 +1149,28 @@ private: static ParseRule *get_rule(GDScriptTokenizer::Token::Type p_token_type); template <class T> - T *alloc_node(); + T *alloc_node() { + T *node = memnew(T); + + node->next = list; + list = node; + + // TODO: Properly set positions for all nodes. + node->start_line = previous.start_line; + node->end_line = previous.end_line; + node->start_column = previous.start_column; + node->end_column = previous.end_column; + node->leftmost_column = previous.leftmost_column; + node->rightmost_column = previous.rightmost_column; + + return node; + } void clear(); void push_error(const String &p_message, const Node *p_origin = nullptr); +#ifdef DEBUG_ENABLED void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1 = String(), const String &p_symbol2 = String(), const String &p_symbol3 = String(), const String &p_symbol4 = String()); void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols); +#endif void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = false); void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force = false); @@ -1246,11 +1270,15 @@ public: void get_annotation_list(List<MethodInfo> *r_annotations) const; const List<ParserError> &get_errors() const { return errors; } - const List<GDScriptWarning> &get_warnings() const { return warnings; } const List<String> get_dependencies() const { // TODO: Keep track of deps. return List<String>(); } +#ifdef DEBUG_ENABLED + const List<GDScriptWarning> &get_warnings() const { return warnings; } + const Set<int> &get_unsafe_lines() const { return unsafe_lines; } + int get_last_line_number() const { return current.end_line; } +#endif GDScriptParser(); ~GDScriptParser(); diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index b3834d97f7..d230173e9a 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -111,6 +111,7 @@ static const char *token_names[] = { "signal", // SIGNAL, "static", // STATIC, "super", // SUPER, + "trait", // TRAIT, "var", // VAR, "void", // VOID, "yield", // YIELD, @@ -435,6 +436,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() { KEYWORD("signal", Token::SIGNAL) \ KEYWORD("static", Token::STATIC) \ KEYWORD("super", Token::SUPER) \ + KEYWORD_GROUP('t') \ + KEYWORD("trait", Token::TRAIT) \ KEYWORD_GROUP('v') \ KEYWORD("var", Token::VAR) \ KEYWORD("void", Token::VOID) \ diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index df691be546..059a226924 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -121,6 +121,7 @@ public: SIGNAL, STATIC, SUPER, + TRAIT, VAR, VOID, YIELD, diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 1e21ac3347..ae7898fdf2 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -290,11 +290,11 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p symbol.script_path = path; symbol.detail = "enum " + String(m.m_enum->identifier->name) + "{"; - for (int i = 0; i < m.m_enum->values.size(); i++) { - if (i > 0) { + for (int j = 0; j < m.m_enum->values.size(); j++) { + if (j > 0) { symbol.detail += ", "; } - symbol.detail += String(m.m_enum->values[i].identifier->name) + " = " + itos(m.m_enum->values[i].value); + symbol.detail += String(m.m_enum->values[j].identifier->name) + " = " + itos(m.m_enum->values[j].value); } symbol.detail += "}"; r_symbol.children.push_back(symbol); |