diff options
Diffstat (limited to 'modules/gdscript')
44 files changed, 601 insertions, 258 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index c8eda53a2d..4981750b7d 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -547,7 +547,7 @@ <return type="void" /> <param index="0" name="icon_path" type="String" /> <description> - Add a custom icon to the current script. After loading an icon at [param icon_path], the icon is displayed in the Scene dock for every node that the script is attached to. For named classes, the icon is also displayed in various editor dialogs. + Add a custom icon to the current script. The script must be registered as a global class using the [code]class_name[/code] keyword for this to have a visible effect. The icon specified at [param icon_path] is displayed in the Scene dock for every node of that class, as well as in various editor dialogs. [codeblock] @icon("res://path/to/class/icon.svg") [/codeblock] diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 60230257e0..b7feedaeaa 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -478,7 +478,7 @@ void GDScript::_clear_doc() { void GDScript::_update_doc() { _clear_doc(); - doc.script_path = "\"" + get_path().get_slice("://", 1) + "\""; + doc.script_path = vformat(R"("%s")", get_script_path().get_slice("://", 1)); if (!name.is_empty()) { doc.name = name; } else { @@ -801,9 +801,9 @@ void GDScript::update_exports() { String GDScript::_get_debug_path() const { if (is_built_in() && !get_name().is_empty()) { - return get_name() + " (" + get_path() + ")"; + return vformat("%s(%s)", get_name(), get_script_path()); } else { - return get_path(); + return get_script_path(); } } @@ -904,7 +904,7 @@ Error GDScript::reload(bool p_keep_state) { for (const GDScriptWarning &warning : parser.get_warnings()) { if (EngineDebugger::is_active()) { Vector<ScriptLanguage::StackInfo> si; - EngineDebugger::get_script_debugger()->send_error("", get_path(), warning.start_line, warning.get_name(), warning.get_message(), false, ERR_HANDLER_WARNING, si); + EngineDebugger::get_script_debugger()->send_error("", get_script_path(), warning.start_line, warning.get_name(), warning.get_message(), false, ERR_HANDLER_WARNING, si); } } #endif @@ -1027,6 +1027,10 @@ void GDScript::set_path(const String &p_path, bool p_take_over) { } } +String GDScript::get_script_path() const { + return path; +} + Error GDScript::load_source_code(const String &p_path) { if (p_path.is_empty() || ResourceLoader::get_resource_type(p_path.get_slice("::", 0)) == "PackedScene") { return OK; @@ -1347,13 +1351,11 @@ void GDScript::_get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScri GDScript::GDScript() : script_list(this) { -#ifdef DEBUG_ENABLED { MutexLock lock(GDScriptLanguage::get_singleton()->mutex); GDScriptLanguage::get_singleton()->script_list.add(&script_list); } -#endif } void GDScript::_save_orphaned_subclasses() { @@ -1487,13 +1489,11 @@ GDScript::~GDScript() { } } -#ifdef DEBUG_ENABLED { MutexLock lock(GDScriptLanguage::get_singleton()->mutex); GDScriptLanguage::get_singleton()->script_list.remove(&script_list); } -#endif if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown. GDScriptCache::remove_script(get_path()); @@ -2019,6 +2019,42 @@ Error GDScriptLanguage::execute_file(const String &p_path) { } void GDScriptLanguage::finish() { + if (_call_stack) { + memdelete_arr(_call_stack); + _call_stack = nullptr; + } + + // Clear the cache before parsing the script_list + GDScriptCache::clear(); + + // Clear dependencies between scripts, to ensure cyclic references are broken + // (to avoid leaks at exit). + SelfList<GDScript> *s = script_list.first(); + while (s) { + // This ensures the current script is not released before we can check + // what's the next one in the list (we can't get the next upfront because we + // don't know if the reference breaking will cause it -or any other after + // it, for that matter- to be released so the next one is not the same as + // before). + Ref<GDScript> scr = s->self(); + if (scr.is_valid()) { + for (KeyValue<StringName, GDScriptFunction *> &E : scr->member_functions) { + GDScriptFunction *func = E.value; + for (int i = 0; i < func->argument_types.size(); i++) { + func->argument_types.write[i].script_type_ref = Ref<Script>(); + } + func->return_type.script_type_ref = Ref<Script>(); + } + for (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) { + E.value.data_type.script_type_ref = Ref<Script>(); + } + + // Clear backup for scripts that could slip out of the cyclic reference + // check + scr->clear(); + } + s = s->next(); + } } void GDScriptLanguage::profiling_start() { @@ -2128,7 +2164,8 @@ void GDScriptLanguage::reload_all_scripts() { SelfList<GDScript> *elem = script_list.first(); while (elem) { - if (elem->self()->get_path().is_resource_file()) { + // Scripts will reload all subclasses, so only reload root scripts. + if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) { print_verbose("GDScript: Found: " + elem->self()->get_path()); scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident } @@ -2157,7 +2194,8 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so SelfList<GDScript> *elem = script_list.first(); while (elem) { - if (elem->self()->get_path().is_resource_file()) { + // Scripts will reload all subclasses, so only reload root scripts. + if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) { scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident } elem = elem->next(); @@ -2530,36 +2568,6 @@ GDScriptLanguage::GDScriptLanguage() { } GDScriptLanguage::~GDScriptLanguage() { - if (_call_stack) { - memdelete_arr(_call_stack); - } - - // Clear dependencies between scripts, to ensure cyclic references are broken (to avoid leaks at exit). - SelfList<GDScript> *s = script_list.first(); - while (s) { - // This ensures the current script is not released before we can check what's the next one - // in the list (we can't get the next upfront because we don't know if the reference breaking - // will cause it -or any other after it, for that matter- to be released so the next one - // is not the same as before). - Ref<GDScript> scr = s->self(); - if (scr.is_valid()) { - for (KeyValue<StringName, GDScriptFunction *> &E : scr->member_functions) { - GDScriptFunction *func = E.value; - for (int i = 0; i < func->argument_types.size(); i++) { - func->argument_types.write[i].script_type_ref = Ref<Script>(); - } - func->return_type.script_type_ref = Ref<Script>(); - } - for (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) { - E.value.data_type.script_type_ref = Ref<Script>(); - } - - // Clear backup for scripts that could slip out of the cyclic reference check - scr->clear(); - } - s = s->next(); - } - singleton = nullptr; } diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 2df89d812c..7911ea47ec 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -240,6 +240,7 @@ public: virtual Error reload(bool p_keep_state = false) override; virtual void set_path(const String &p_path, bool p_take_over = false) override; + String get_script_path() const; Error load_source_code(const String &p_path); Error load_byte_code(const String &p_path); @@ -432,7 +433,7 @@ public: csi.write[_debug_call_stack_pos - i - 1].line = _call_stack[i].line ? *_call_stack[i].line : 0; if (_call_stack[i].function) { csi.write[_debug_call_stack_pos - i - 1].func = _call_stack[i].function->get_name(); - csi.write[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_path(); + csi.write[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_script_path(); } } return csi; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 584bb74e4f..2892ae3f4e 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -196,8 +196,11 @@ Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::C while (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::CLASS) { GDScriptParser::ClassNode *current_class_node = current_data_type->class_type; if (has_member_name_conflict_in_script_class(p_member_name, current_class_node, p_member_node)) { - push_error(vformat(R"(The member "%s" already exists in parent class %s.)", p_member_name, current_class_node->identifier->name), - p_member_node); + String parent_class_name = current_class_node->fqcn; + if (current_class_node->identifier != nullptr) { + parent_class_name = current_class_node->identifier->name; + } + push_error(vformat(R"(The member "%s" already exists in parent class %s.)", p_member_name, parent_class_name), p_member_node); return ERR_PARSE_ERROR; } current_data_type = ¤t_class_node->base_type; @@ -216,6 +219,22 @@ Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::C return OK; } +void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list) { + if (p_list->find(p_node) != nullptr) { + return; + } + p_list->push_back(p_node); + + // Prioritize node base type over its outer class + if (p_node->base_type.class_type != nullptr) { + get_class_node_current_scope_classes(p_node->base_type.class_type, p_list); + } + + if (p_node->outer != nullptr) { + get_class_node_current_scope_classes(p_node->outer, p_list); + } +} + Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) { if (p_class->base_type.is_set()) { // Already resolved @@ -324,9 +343,10 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, base.native_type = name; } else { // Look for other classes in script. - GDScriptParser::ClassNode *look_class = p_class; bool found = false; - while (look_class != nullptr) { + List<GDScriptParser::ClassNode *> script_classes; + get_class_node_current_scope_classes(p_class, &script_classes); + for (GDScriptParser::ClassNode *look_class : script_classes) { if (look_class->identifier && look_class->identifier->name == name) { if (!look_class->get_datatype().is_set()) { Error err = resolve_inheritance(look_class, false); @@ -350,7 +370,6 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, found = true; break; } - look_class = look_class->outer; } if (!found) { @@ -494,8 +513,8 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type result = ref->get_parser()->head->get_datatype(); } else { result.kind = GDScriptParser::DataType::SCRIPT; - result.native_type = ScriptServer::get_global_class_native_base(first); result.script_type = ResourceLoader::load(path, "Script"); + result.native_type = result.script_type->get_instance_base_type(); result.script_path = path; result.is_constant = true; result.is_meta_type = false; @@ -514,12 +533,11 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type result = make_native_enum_type(parser->current_class->base_type.native_type, first); } else { // Classes in current scope. - GDScriptParser::ClassNode *script_class = parser->current_class; - bool found = false; - while (!found && script_class != nullptr) { + List<GDScriptParser::ClassNode *> script_classes; + get_class_node_current_scope_classes(parser->current_class, &script_classes); + for (GDScriptParser::ClassNode *script_class : script_classes) { if (script_class->identifier && script_class->identifier->name == first) { result = script_class->get_datatype(); - found = true; break; } if (script_class->members_indices.has(first)) { @@ -527,24 +545,21 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type switch (member.type) { case GDScriptParser::ClassNode::Member::CLASS: result = member.m_class->get_datatype(); - found = true; break; case GDScriptParser::ClassNode::Member::ENUM: result = member.m_enum->get_datatype(); - found = true; break; case GDScriptParser::ClassNode::Member::CONSTANT: if (member.constant->get_datatype().is_meta_type) { result = member.constant->get_datatype(); result.is_meta_type = false; - found = true; break; } else if (Ref<Script>(member.constant->initializer->reduced_value).is_valid()) { Ref<GDScript> gdscript = member.constant->initializer->reduced_value; if (gdscript.is_valid()) { - Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_path()); + Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_script_path()); if (ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) { - push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_path()), p_type); + push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), p_type); return GDScriptParser::DataType(); } result = ref->get_parser()->head->get_datatype(); @@ -566,7 +581,6 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return GDScriptParser::DataType(); } } - script_class = script_class->outer; } } if (!result.is_set()) { @@ -1162,6 +1176,8 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * if (p_function->parameters[i]->default_value->is_constant) { p_function->default_arg_values.push_back(p_function->parameters[i]->default_value->reduced_value); + } else { + p_function->default_arg_values.push_back(Variant()); // Prevent shift. } } #endif // TOOLS_ENABLED @@ -1214,11 +1230,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * if (!valid) { // Compute parent signature as a string to show in the error message. - String parent_signature = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant"; - if (parent_signature == "null") { - parent_signature = "void"; - } - parent_signature += " " + p_function->identifier->name.operator String() + "("; + String parent_signature = p_function->identifier->name.operator String() + "("; int j = 0; for (const GDScriptParser::DataType &par_type : parameters_types) { if (j > 0) { @@ -1235,7 +1247,15 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * j++; } - parent_signature += ")"; + parent_signature += ") -> "; + + const String return_type = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant"; + if (return_type == "null") { + parent_signature += "void"; + } else { + parent_signature += return_type; + } + push_error(vformat(R"(The function signature doesn't match the parent. Parent signature is "%s".)", parent_signature), p_function); } } @@ -1725,7 +1745,6 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame } else { result.type_source = GDScriptParser::DataType::INFERRED; } - result.is_constant = false; } if (p_parameter->datatype_specifier != nullptr) { @@ -1745,6 +1764,7 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is "null".)", p_parameter->identifier->name), p_parameter->default_value); } + result.is_constant = false; p_parameter->set_datatype(result); } @@ -2563,8 +2583,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a parser->push_warning(p_call, GDScriptWarning::RETURN_VALUE_DISCARDED, p_call->function_name); } - if (is_static && !base_type.is_meta_type && !(callee_type != GDScriptParser::Node::SUBSCRIPT && parser->current_function != nullptr && parser->current_function->is_static)) { - parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, base_type.to_string()); + if (is_static && !base_type.is_meta_type && !(is_self && parser->current_function != nullptr && parser->current_function->is_static)) { + String caller_type = String(base_type.native_type); + + if (caller_type.is_empty()) { + caller_type = base_type.to_string(); + } + + parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, caller_type); } #endif // DEBUG_ENABLED @@ -2661,7 +2687,7 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { } void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) { - HashMap<Variant, GDScriptParser::ExpressionNode *, VariantHasher, VariantComparator> elements; + HashMap<Variant, GDScriptParser::ExpressionNode *, VariantHasher, StringLikeVariantComparator> elements; for (int i = 0; i < p_dictionary->elements.size(); i++) { const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; @@ -2727,21 +2753,13 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str return type; } - type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - type.kind = GDScriptParser::DataType::CLASS; - type.builtin_type = Variant::OBJECT; - type.native_type = ScriptServer::get_global_class_native_base(p_class_name); - type.class_type = ref->get_parser()->head; - type.script_path = ref->get_parser()->script_path; - type.is_constant = true; - type.is_meta_type = true; - return type; + return ref->get_parser()->head->get_datatype(); } else { type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; type.kind = GDScriptParser::DataType::SCRIPT; type.builtin_type = Variant::OBJECT; - type.native_type = ScriptServer::get_global_class_native_base(p_class_name); type.script_type = ResourceLoader::load(path, "Script"); + type.native_type = type.script_type->get_instance_base_type(); type.script_path = path; type.is_constant = true; type.is_meta_type = true; @@ -2884,41 +2902,43 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } // Check outer constants. // TODO: Allow outer static functions. - GDScriptParser::ClassNode *outer = base_class->outer; - while (outer != nullptr) { - if (outer->has_member(name)) { - const GDScriptParser::ClassNode::Member &member = outer->get_member(name); - switch (member.type) { - case GDScriptParser::ClassNode::Member::CONSTANT: { - // TODO: Make sure loops won't cause problem. And make special error message for those. - // For out-of-order resolution: - reduce_expression(member.constant->initializer); - p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = true; - p_identifier->reduced_value = member.constant->initializer->reduced_value; - return; - } break; - case GDScriptParser::ClassNode::Member::ENUM_VALUE: { - p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = true; - p_identifier->reduced_value = member.enum_value.value; - return; - } break; - case GDScriptParser::ClassNode::Member::ENUM: { - p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = false; - return; - } break; - case GDScriptParser::ClassNode::Member::CLASS: { - resolve_class_interface(member.m_class); - p_identifier->set_datatype(member.m_class->get_datatype()); - return; - } break; - default: - break; + if (base_class->outer != nullptr) { + List<GDScriptParser::ClassNode *> script_classes; + get_class_node_current_scope_classes(parser->current_class, &script_classes); + for (GDScriptParser::ClassNode *script_class : script_classes) { + if (script_class->has_member(name)) { + const GDScriptParser::ClassNode::Member &member = script_class->get_member(name); + switch (member.type) { + case GDScriptParser::ClassNode::Member::CONSTANT: { + // TODO: Make sure loops won't cause problem. And make special error message for those. + // For out-of-order resolution: + reduce_expression(member.constant->initializer); + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.constant->initializer->reduced_value; + return; + } break; + case GDScriptParser::ClassNode::Member::ENUM_VALUE: { + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.enum_value.value; + return; + } break; + case GDScriptParser::ClassNode::Member::ENUM: { + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = false; + return; + } break; + case GDScriptParser::ClassNode::Member::CLASS: { + resolve_class_interface(member.m_class); + p_identifier->set_datatype(member.m_class->get_datatype()); + return; + } break; + default: + break; + } } } - outer = outer->outer; } base_class = base_class->base_type.class_type; @@ -3125,14 +3145,19 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident } } } else if (ResourceLoader::get_resource_type(autoload.path) == "PackedScene") { - Error err = OK; - Ref<GDScript> scr = GDScriptCache::get_packed_scene_script(autoload.path, err); - if (err == OK && scr.is_valid()) { - Ref<GDScriptParserRef> singl_parser = get_parser_for(scr->get_path()); - if (singl_parser.is_valid()) { - err = singl_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); - if (err == OK) { - result = type_from_metatype(singl_parser->get_parser()->head->get_datatype()); + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(name)) { + Variant constant = GDScriptLanguage::get_singleton()->get_named_globals_map()[name]; + Node *node = Object::cast_to<Node>(constant); + if (node != nullptr) { + Ref<GDScript> scr = node->get_script(); + if (scr.is_valid()) { + Ref<GDScriptParserRef> singl_parser = get_parser_for(scr->get_script_path()); + if (singl_parser.is_valid()) { + Error err = singl_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + if (err == OK) { + result = type_from_metatype(singl_parser->get_parser()->head->get_datatype()); + } + } } } } @@ -3329,7 +3354,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri Ref<GDScript> gdscr = Ref<GDScript>(p_subscript->base->reduced_value); if (!valid && gdscr.is_valid()) { Error err = OK; - GDScriptCache::get_full_script(gdscr->get_path(), err); + GDScriptCache::get_full_script(gdscr->get_script_path(), err); if (err == OK) { value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid); } @@ -3420,7 +3445,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::QUATERNION: case Variant::AABB: case Variant::OBJECT: - error = index_type.builtin_type != Variant::STRING; + error = index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME; break; // Expect String or number. case Variant::BASIS: @@ -3434,11 +3459,11 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::TRANSFORM3D: case Variant::PROJECTION: error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT && - index_type.builtin_type != Variant::STRING; + index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME; break; // Expect String or int. case Variant::COLOR: - error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING; + error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME; break; // Don't support indexing, but we will check it later. case Variant::RID: @@ -3702,7 +3727,13 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va result.builtin_type = p_value.get_type(); result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; // Constant has explicit type. - if (p_value.get_type() == Variant::OBJECT) { + if (p_value.get_type() == Variant::NIL) { + // A null value is a variant, not void. + result.kind = GDScriptParser::DataType::VARIANT; + } else if (p_value.get_type() == Variant::OBJECT) { + // Object is treated as a native type, not a builtin type. + result.kind = GDScriptParser::DataType::NATIVE; + Object *obj = p_value; if (!obj) { return GDScriptParser::DataType(); @@ -4152,6 +4183,8 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ if (p_target.kind == GDScriptParser::DataType::BUILTIN) { bool valid = p_source.kind == GDScriptParser::DataType::BUILTIN && p_target.builtin_type == p_source.builtin_type; + valid |= p_source.builtin_type == Variant::STRING && p_target.builtin_type == Variant::STRING_NAME; + valid |= p_source.builtin_type == Variant::STRING_NAME && p_target.builtin_type == Variant::STRING; if (!valid && p_allow_implicit_conversion) { valid = Variant::can_convert_strict(p_source.builtin_type, p_target.builtin_type); } diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 23a3ad39a5..44ca1593ed 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -50,6 +50,8 @@ class GDScriptAnalyzer { Error check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string); Error check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node); + void get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list); + Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true); GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index fa158591fd..1bc83fbbb5 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -164,7 +164,7 @@ void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName function->name = p_function_name; function->_script = p_script; - function->source = p_script->get_path(); + function->source = p_script->get_script_path(); #ifdef DEBUG_ENABLED function->func_cname = (String(function->source) + " - " + String(p_function_name)).utf8(); diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index f35318e4c6..d1467eea95 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -128,6 +128,10 @@ void GDScriptCache::move_script(const String &p_from, const String &p_to) { MutexLock lock(singleton->mutex); + if (singleton->cleared) { + return; + } + for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) { if (E.value.has(p_from)) { E.value.insert(p_to); @@ -158,6 +162,10 @@ void GDScriptCache::remove_script(const String &p_path) { MutexLock lock(singleton->mutex); + if (singleton->cleared) { + return; + } + for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) { if (!E.value.has(p_path)) { continue; @@ -260,7 +268,7 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro Ref<GDScript> script; r_error = OK; if (singleton->full_gdscript_cache.has(p_path)) { - script = Ref<GDScript>(singleton->full_gdscript_cache[p_path]); + script = singleton->full_gdscript_cache[p_path]; if (!p_update_from_disk) { return script; } @@ -360,36 +368,10 @@ Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_ singleton->packed_scene_cache[p_path] = scene; singleton->packed_scene_dependencies[p_path].insert(p_owner); - scene->recreate_state(); scene->reload_from_file(); return scene; } -Ref<GDScript> GDScriptCache::get_packed_scene_script(const String &p_path, Error &r_error) { - r_error = OK; - Ref<PackedScene> scene = get_packed_scene(p_path, r_error); - - if (r_error != OK) { - return Ref<GDScript>(); - } - - int node_count = scene->get_state()->get_node_count(); - if (node_count == 0) { - return Ref<GDScript>(); - } - - const int ROOT_NODE = 0; - for (int i = 0; i < scene->get_state()->get_node_property_count(ROOT_NODE); i++) { - if (scene->get_state()->get_node_property_name(ROOT_NODE, i) != SNAME("script")) { - continue; - } - - return scene->get_state()->get_node_property_value(ROOT_NODE, i); - } - - return Ref<GDScript>(); -} - void GDScriptCache::clear_unreferenced_packed_scenes() { if (singleton == nullptr) { return; @@ -397,6 +379,10 @@ void GDScriptCache::clear_unreferenced_packed_scenes() { MutexLock lock(singleton->mutex); + if (singleton->cleared) { + return; + } + for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) { if (E.value.size() > 0 || !ResourceLoader::is_imported(E.key)) { continue; @@ -407,15 +393,20 @@ void GDScriptCache::clear_unreferenced_packed_scenes() { } } -GDScriptCache::GDScriptCache() { - singleton = this; -} +void GDScriptCache::clear() { + if (singleton == nullptr) { + return; + } -GDScriptCache::~GDScriptCache() { - destructing = true; + MutexLock lock(singleton->mutex); + + if (singleton->cleared) { + return; + } + singleton->cleared = true; RBSet<Ref<GDScriptParserRef>> parser_map_refs; - for (KeyValue<String, GDScriptParserRef *> &E : parser_map) { + for (KeyValue<String, GDScriptParserRef *> &E : singleton->parser_map) { parser_map_refs.insert(E.value); } @@ -424,13 +415,25 @@ GDScriptCache::~GDScriptCache() { E->clear(); } + singleton->packed_scene_dependencies.clear(); + singleton->packed_scene_cache.clear(); + parser_map_refs.clear(); - parser_map.clear(); - shallow_gdscript_cache.clear(); - full_gdscript_cache.clear(); + singleton->parser_map.clear(); + singleton->shallow_gdscript_cache.clear(); + singleton->full_gdscript_cache.clear(); + + singleton->packed_scene_cache.clear(); + singleton->packed_scene_dependencies.clear(); +} - packed_scene_cache.clear(); - packed_scene_dependencies.clear(); +GDScriptCache::GDScriptCache() { + singleton = this; +} +GDScriptCache::~GDScriptCache() { + if (!cleared) { + clear(); + } singleton = nullptr; } diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index 0f9d87aa67..0ee269f96c 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -87,7 +87,7 @@ class GDScriptCache { static GDScriptCache *singleton; - bool destructing = false; + bool cleared = false; Mutex mutex; @@ -102,15 +102,9 @@ public: static Error finish_compiling(const String &p_owner); static Ref<PackedScene> get_packed_scene(const String &p_path, Error &r_error, const String &p_owner = ""); - static Ref<GDScript> get_packed_scene_script(const String &p_path, Error &r_error); static void clear_unreferenced_packed_scenes(); - static bool is_destructing() { - if (singleton == nullptr) { - return true; - } - return singleton->destructing; - }; + static void clear(); GDScriptCache(); ~GDScriptCache(); diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index f0ceb42f89..c0539c7e45 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -43,7 +43,7 @@ bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringN return false; } - if (codegen.parameters.has(p_name) || codegen.locals.has(p_name)) { + if (_is_local_or_parameter(codegen, p_name)) { return false; //shadowed } @@ -65,6 +65,10 @@ bool GDScriptCompiler::_is_class_member_property(GDScript *owner, const StringNa return ClassDB::has_property(nc->get_name(), p_name); } +bool GDScriptCompiler::_is_local_or_parameter(CodeGen &codegen, const StringName &p_name) { + return codegen.parameters.has(p_name) || codegen.locals.has(p_name); +} + void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::Node *p_node) { if (!error.is_empty()) { return; @@ -920,7 +924,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code StringName var_name = identifier->name; if (_is_class_member_property(codegen, var_name)) { assign_class_member_property = var_name; - } else if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) { + } else if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) { is_member_property = true; member_property_setter_function = codegen.script->member_indices[var_name].setter; member_property_has_setter = member_property_setter_function != StringName(); @@ -1131,7 +1135,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code bool is_in_setter = false; StringName setter_function; StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name; - if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) { + if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) { is_member = true; setter_function = codegen.script->member_indices[var_name].setter; has_setter = setter_function != StringName(); @@ -1252,9 +1256,30 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c equality_type.kind = GDScriptDataType::BUILTIN; equality_type.builtin_type = Variant::BOOL; + GDScriptCodeGenerator::Address type_string_addr = codegen.add_constant(Variant::STRING); + GDScriptCodeGenerator::Address type_string_name_addr = codegen.add_constant(Variant::STRING_NAME); + // Check type equality. GDScriptCodeGenerator::Address type_equality_addr = codegen.add_temporary(equality_type); codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_EQUAL, p_type_addr, literal_type_addr); + + // Check if StringName <-> String comparison is possible. + GDScriptCodeGenerator::Address type_comp_addr_1 = codegen.add_temporary(equality_type); + GDScriptCodeGenerator::Address type_comp_addr_2 = codegen.add_temporary(equality_type); + + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_EQUAL, p_type_addr, type_string_addr); + codegen.generator->write_binary_operator(type_comp_addr_2, Variant::OP_EQUAL, literal_type_addr, type_string_name_addr); + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_AND, type_comp_addr_1, type_comp_addr_2); + codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_OR, type_equality_addr, type_comp_addr_1); + + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_EQUAL, p_type_addr, type_string_name_addr); + codegen.generator->write_binary_operator(type_comp_addr_2, Variant::OP_EQUAL, literal_type_addr, type_string_addr); + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_AND, type_comp_addr_1, type_comp_addr_2); + codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_OR, type_equality_addr, type_comp_addr_1); + + codegen.generator->pop_temporary(); // Remove type_comp_addr_2 from stack. + codegen.generator->pop_temporary(); // Remove type_comp_addr_1 from stack. + codegen.generator->write_and_left_operand(type_equality_addr); // Get literal. @@ -1875,6 +1900,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui GDScriptCodeGenerator::Address local = codegen.locals[lv->identifier->name]; GDScriptDataType local_type = _gdtype_from_datatype(lv->get_datatype(), codegen.script); + bool initialized = false; if (lv->initializer != nullptr) { // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. if (local_type.has_type && local_type.builtin_type == Variant::ARRAY) { @@ -1896,15 +1922,23 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } + initialized = true; } else if (local_type.has_type) { // Initialize with default for type. if (local_type.has_container_element_type()) { codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); + initialized = true; } else if (local_type.kind == GDScriptDataType::BUILTIN) { codegen.generator->write_construct(local, local_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); + initialized = true; } // The `else` branch is for objects, in such case we leave it as `null`. } + + // Assigns a null for the unassigned variables in loops. + if (!initialized && p_block->is_loop) { + codegen.generator->write_construct(local, Variant::NIL, Vector<GDScriptCodeGenerator::Address>()); + } } break; case GDScriptParser::Node::CONSTANT: { // Local constants. @@ -2083,8 +2117,8 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ if (EngineDebugger::is_active()) { String signature; // Path. - if (!p_script->get_path().is_empty()) { - signature += p_script->get_path(); + if (!p_script->get_script_path().is_empty()) { + signature += p_script->get_script_path(); } // Location. if (p_func) { @@ -2284,7 +2318,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri _set_error(vformat(R"(Could not find class "%s" in "%s".)", base->fully_qualified_name, base->path), nullptr); return ERR_COMPILATION_FAILED; } - ERR_FAIL_COND_V(!base->is_valid(), ERR_BUG); + ERR_FAIL_COND_V(!base->is_valid() && !base->reloading, ERR_BUG); } p_script->base = base; diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 45ca4fe342..cba585e5a5 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -115,6 +115,7 @@ class GDScriptCompiler { bool _is_class_member_property(CodeGen &codegen, const StringName &p_name); bool _is_class_member_property(GDScript *owner, const StringName &p_name); + bool _is_local_or_parameter(CodeGen &codegen, const StringName &p_name); void _set_error(const String &p_error, const GDScriptParser::Node *p_node); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 7628bffd22..693863ab38 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1612,7 +1612,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, } } - if (!found) { + if (!found && base.value.get_type() != Variant::NIL) { found = _guess_method_return_type_from_base(c, base, call->function_name, r_type); } } @@ -2512,50 +2512,62 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::SubscriptNode *p_subscript, GDScriptParser::DataType &r_base_type, Variant *r_base = nullptr) { - if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER && p_context.base != nullptr) { - const GDScriptParser::GetNodeNode *get_node = nullptr; - const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base); + if (p_context.base == nullptr) { + return false; + } + const GDScriptParser::GetNodeNode *get_node = nullptr; + + switch (p_subscript->base->type) { + case GDScriptParser::Node::GET_NODE: { + get_node = static_cast<GDScriptParser::GetNodeNode *>(p_subscript->base); + } break; + + case GDScriptParser::Node::IDENTIFIER: { + const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base); - switch (identifier_node->source) { - case GDScriptParser::IdentifierNode::Source::MEMBER_VARIABLE: { - if (p_context.current_class != nullptr) { - const StringName &member_name = identifier_node->name; - const GDScriptParser::ClassNode *current_class = p_context.current_class; + switch (identifier_node->source) { + case GDScriptParser::IdentifierNode::Source::MEMBER_VARIABLE: { + if (p_context.current_class != nullptr) { + const StringName &member_name = identifier_node->name; + const GDScriptParser::ClassNode *current_class = p_context.current_class; - if (current_class->has_member(member_name)) { - const GDScriptParser::ClassNode::Member &member = current_class->get_member(member_name); + if (current_class->has_member(member_name)) { + const GDScriptParser::ClassNode::Member &member = current_class->get_member(member_name); - if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { - const GDScriptParser::VariableNode *variable = static_cast<GDScriptParser::VariableNode *>(member.variable); + if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { + const GDScriptParser::VariableNode *variable = static_cast<GDScriptParser::VariableNode *>(member.variable); - if (variable->initializer && variable->initializer->type == GDScriptParser::Node::GET_NODE) { - get_node = static_cast<GDScriptParser::GetNodeNode *>(variable->initializer); + if (variable->initializer && variable->initializer->type == GDScriptParser::Node::GET_NODE) { + get_node = static_cast<GDScriptParser::GetNodeNode *>(variable->initializer); + } } } } - } - } break; - case GDScriptParser::IdentifierNode::Source::LOCAL_VARIABLE: { - if (identifier_node->next != nullptr && identifier_node->next->type == GDScriptParser::ClassNode::Node::GET_NODE) { - get_node = static_cast<GDScriptParser::GetNodeNode *>(identifier_node->next); - } - } break; - default: - break; - } + } break; + case GDScriptParser::IdentifierNode::Source::LOCAL_VARIABLE: { + if (identifier_node->next != nullptr && identifier_node->next->type == GDScriptParser::ClassNode::Node::GET_NODE) { + get_node = static_cast<GDScriptParser::GetNodeNode *>(identifier_node->next); + } + } break; + default: { + } break; + } + } break; + default: { + } break; + } - if (get_node != nullptr) { - const Object *node = p_context.base->call("get_node_or_null", NodePath(get_node->full_path)); - if (node != nullptr) { - if (r_base != nullptr) { - *r_base = node; - } - r_base_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - r_base_type.kind = GDScriptParser::DataType::NATIVE; - r_base_type.native_type = node->get_class_name(); - r_base_type.builtin_type = Variant::OBJECT; - return true; + if (get_node != nullptr) { + const Object *node = p_context.base->call("get_node_or_null", NodePath(get_node->full_path)); + if (node != nullptr) { + if (r_base != nullptr) { + *r_base = node; } + r_base_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_base_type.kind = GDScriptParser::DataType::NATIVE; + r_base_type.native_type = node->get_class_name(); + r_base_type.builtin_type = Variant::OBJECT; + return true; } } @@ -2612,7 +2624,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } } - if (p_context.base != nullptr && subscript->is_attribute) { + if (subscript->is_attribute) { bool found_type = _get_subscript_type(p_context, subscript, base_type, &base); if (!found_type) { @@ -3265,15 +3277,6 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } } - // Need special checks for assert and preload as they are technically - // keywords, so are not registered in GDScriptUtilityFunctions. - if (GDScriptUtilityFunctions::function_exists(p_symbol) || "assert" == p_symbol || "preload" == p_symbol) { - r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; - r_result.class_name = "@GDScript"; - r_result.class_member = p_symbol; - return OK; - } - if ("PI" == p_symbol || "TAU" == p_symbol || "INF" == p_symbol || "NAN" == p_symbol) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; r_result.class_name = "@GDScript"; @@ -3283,10 +3286,24 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co GDScriptParser parser; parser.parse(p_code, p_path, true); - GDScriptAnalyzer analyzer(&parser); - analyzer.analyze(); GDScriptParser::CompletionContext context = parser.get_completion_context(); + context.base = p_owner; + + // Allows class functions with the names like built-ins to be handled properly. + if (context.type != GDScriptParser::COMPLETION_ATTRIBUTE) { + // Need special checks for assert and preload as they are technically + // keywords, so are not registered in GDScriptUtilityFunctions. + if (GDScriptUtilityFunctions::function_exists(p_symbol) || "assert" == p_symbol || "preload" == p_symbol) { + r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; + r_result.class_name = "@GDScript"; + r_result.class_member = p_symbol; + return OK; + } + } + + GDScriptAnalyzer analyzer(&parser); + analyzer.analyze(); if (context.current_class && context.current_class->extends.size() > 0) { bool success = false; @@ -3444,7 +3461,9 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co break; } GDScriptCompletionIdentifier base; - if (!_guess_expression_type(context, subscript->base, base)) { + + bool found_type = _get_subscript_type(context, subscript, base.type); + if (!found_type && !_guess_expression_type(context, subscript->base, base)) { break; } diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index d24cba4c59..f2aafe9f0c 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -148,7 +148,9 @@ GDScriptParser::GDScriptParser() { // Networking. register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("", "", "", 0), true); +#ifdef DEBUG_ENABLED is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable"); +#endif } GDScriptParser::~GDScriptParser() { @@ -1833,9 +1835,9 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { } suite->add_local(SuiteNode::Local(n_for->variable, current_function)); } - suite->parent_for = n_for; n_for->loop = parse_suite(R"("for" block)", suite); + n_for->loop->is_loop = true; complete_extents(n_for); // Reset break/continue state. @@ -2167,6 +2169,7 @@ GDScriptParser::WhileNode *GDScriptParser::parse_while() { is_continue_match = false; n_while->loop = parse_suite(R"("while" block)"); + n_while->loop->is_loop = true; complete_extents(n_while); // Reset break/continue state. @@ -3738,6 +3741,12 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node // This is called after the analyzer is done finding the type, so this should be set here. DataType export_type = variable->get_datatype(); + if (p_annotation->name == SNAME("@export_range")) { + if (export_type.builtin_type == Variant::INT) { + variable->export_info.type = Variant::INT; + } + } + if (p_annotation->name == SNAME("@export")) { if (variable->datatype_specifier == nullptr && variable->initializer == nullptr) { push_error(R"(Cannot use simple "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation); @@ -3777,15 +3786,14 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } break; case GDScriptParser::DataType::CLASS: - // Can assume type is a global GDScript class. if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; - variable->export_info.hint_string = export_type.class_type->identifier->name; + variable->export_info.hint_string = export_type.to_string(); } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_NODE_TYPE; - variable->export_info.hint_string = export_type.class_type->identifier->name; + variable->export_info.hint_string = export_type.to_string(); } else { push_error(R"(Export type can only be built-in, a resource, a node or an enum.)", variable); return false; @@ -3794,16 +3802,19 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node break; case GDScriptParser::DataType::SCRIPT: { StringName class_name; - if (export_type.script_type != nullptr && export_type.script_type.is_valid()) { + StringName native_base; + if (export_type.script_type.is_valid()) { class_name = export_type.script_type->get_language()->get_global_class_name(export_type.script_type->get_path()); + native_base = export_type.script_type->get_instance_base_type(); } if (class_name == StringName()) { Ref<Script> script = ResourceLoader::load(export_type.script_path, SNAME("Script")); if (script.is_valid()) { class_name = script->get_language()->get_global_class_name(export_type.script_path); + native_base = script->get_instance_base_type(); } } - if (class_name != StringName() && ClassDB::is_parent_class(ScriptServer::get_global_class_native_base(class_name), SNAME("Resource"))) { + if (class_name != StringName() && native_base != StringName() && ClassDB::is_parent_class(native_base, SNAME("Resource"))) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; variable->export_info.hint_string = class_name; diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 7baa3ca3d9..d092a2a5e9 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1055,12 +1055,12 @@ public: HashMap<StringName, int> locals_indices; FunctionNode *parent_function = nullptr; - ForNode *parent_for = nullptr; IfNode *parent_if = nullptr; bool has_return = false; bool has_continue = false; bool has_unreachable_code = false; // Just so warnings aren't given more than once per block. + bool is_loop = false; bool has_local(const StringName &p_name) const; const Local &get_local(const StringName &p_name) const; @@ -1217,13 +1217,14 @@ private: bool can_break = false; bool can_continue = false; bool is_continue_match = false; // Whether a `continue` will act on a `match`. - bool is_ignoring_warnings = false; List<bool> multiline_stack; ClassNode *head = nullptr; Node *list = nullptr; List<ParserError> errors; + #ifdef DEBUG_ENABLED + bool is_ignoring_warnings = false; List<GDScriptWarning> warnings; HashSet<String> ignored_warnings; HashSet<uint32_t> ignored_warning_codes; diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index bcbe8b8d2b..27b6792e84 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -294,6 +294,7 @@ struct GDScriptUtilityFunctionsDefinitions { } GDScript *p = base.ptr(); + String path = p->get_script_path(); Vector<StringName> sname; while (p->_owner) { @@ -302,7 +303,7 @@ struct GDScriptUtilityFunctionsDefinitions { } sname.reverse(); - if (!p->path.is_resource_file()) { + if (!path.is_resource_file()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::DICTIONARY; @@ -317,7 +318,7 @@ struct GDScriptUtilityFunctionsDefinitions { Dictionary d; d["@subpath"] = cp; - d["@path"] = p->get_path(); + d["@path"] = path; for (const KeyValue<StringName, GDScript::MemberInfo> &E : base->member_indices) { if (!d.has(E.key)) { diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index c73ba798aa..fdcc0625d7 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -2227,7 +2227,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } #ifdef DEBUG_ENABLED gdfs->state.function_name = name; - gdfs->state.script_path = _script->get_path(); + gdfs->state.script_path = _script->get_script_path(); #endif gdfs->state.defarg = defarg; gdfs->function = this; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index de3becbaf8..e442bf8159 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -844,8 +844,9 @@ Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) { lines = p_code.split("\n"); Error err = GDScriptParser::parse(p_code, p_path, false); + GDScriptAnalyzer analyzer(this); + if (err == OK) { - GDScriptAnalyzer analyzer(this); err = analyzer.analyze(); } update_diagnostics(); diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 39f4c976a4..551973140d 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -32,6 +32,7 @@ #include "core/config/project_settings.h" #include "editor/doc_tools.h" +#include "editor/editor_help.h" #include "editor/editor_log.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 7f42643c8f..f59983ca90 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -71,27 +71,38 @@ void init_autoloads() { continue; } - Ref<Resource> res = ResourceLoader::load(info.path); - ERR_CONTINUE_MSG(res.is_null(), "Can't autoload: " + info.path); Node *n = nullptr; - Ref<PackedScene> scn = res; - Ref<Script> script = res; - if (scn.is_valid()) { - n = scn->instantiate(); - } else if (script.is_valid()) { - StringName ibt = script->get_instance_base_type(); - bool valid_type = ClassDB::is_parent_class(ibt, "Node"); - ERR_CONTINUE_MSG(!valid_type, "Script does not inherit from Node: " + info.path); + if (ResourceLoader::get_resource_type(info.path) == "PackedScene") { + // Cache the scene reference before loading it (for cyclic references) + Ref<PackedScene> scn; + scn.instantiate(); + scn->set_path(info.path); + scn->reload_from_file(); + ERR_CONTINUE_MSG(!scn.is_valid(), vformat("Can't autoload: %s.", info.path)); + + if (scn.is_valid()) { + n = scn->instantiate(); + } + } else { + Ref<Resource> res = ResourceLoader::load(info.path); + ERR_CONTINUE_MSG(res.is_null(), vformat("Can't autoload: %s.", info.path)); - Object *obj = ClassDB::instantiate(ibt); + Ref<Script> scr = res; + if (scr.is_valid()) { + StringName ibt = scr->get_instance_base_type(); + bool valid_type = ClassDB::is_parent_class(ibt, "Node"); + ERR_CONTINUE_MSG(!valid_type, vformat("Script does not inherit from Node: %s.", info.path)); - ERR_CONTINUE_MSG(!obj, "Cannot instance script for autoload, expected 'Node' inheritance, got: " + String(ibt) + "."); + Object *obj = ClassDB::instantiate(ibt); - n = Object::cast_to<Node>(obj); - n->set_script(script); + ERR_CONTINUE_MSG(!obj, vformat("Cannot instance script for Autoload, expected 'Node' inheritance, got: %s.", ibt)); + + n = Object::cast_to<Node>(obj); + n->set_script(scr); + } } - ERR_CONTINUE_MSG(!n, "Path in autoload not a node or script: " + info.path); + ERR_CONTINUE_MSG(!n, vformat("Path in autoload not a node or script: %s.", info.path)); n->set_name(info.name); for (int i = 0; i < ScriptServer::get_language_count(); i++) { diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.gd new file mode 100644 index 0000000000..4dd2b556ee --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.gd @@ -0,0 +1,9 @@ +# https://github.com/godotengine/godot/issues/62957 + +func test(): + var dict = { + &"key": "StringName", + "key": "String" + } + + print("Invalid dictionary: %s" % dict) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.out new file mode 100644 index 0000000000..189d8a7955 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Key "key" was already used in this dictionary (at line 5). diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out index 3baeb17066..4ccd2da381 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function(int)". +The function signature doesn't match the parent. Parent signature is "my_function(int) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out index 3baeb17066..4ccd2da381 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function(int)". +The function signature doesn't match the parent. Parent signature is "my_function(int) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out index 665c229339..c70a1df10d 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function(int = default)". +The function signature doesn't match the parent. Parent signature is "my_function(int = default) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out index 3baeb17066..4ccd2da381 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function(int)". +The function signature doesn't match the parent. Parent signature is "my_function(int) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out index 5b22739a93..61004ff627 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function()". +The function signature doesn't match the parent. Parent signature is "my_function() -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.gd new file mode 100644 index 0000000000..4511c3d10b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.gd @@ -0,0 +1,8 @@ +func test(): + # Converted to String when initialized + var string_array: Array[String] = [&"abc"] + print(string_array) + + # Converted to StringName when initialized + var stringname_array: Array[StringName] = ["abc"] + print(stringname_array) diff --git a/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.out new file mode 100644 index 0000000000..70dd01d88e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.out @@ -0,0 +1,3 @@ +GDTEST_OK +["abc"] +[&"abc"] diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.gd new file mode 100644 index 0000000000..7881a0feb6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.gd @@ -0,0 +1,14 @@ +const A: = preload("base_outer_resolution_a.notest.gd") +const B: = preload("base_outer_resolution_b.notest.gd") +const C: = preload("base_outer_resolution_c.notest.gd") + +const Extend: = preload("base_outer_resolution_extend.notest.gd") + +func test() -> void: + Extend.test_a(A.new()) + Extend.test_b(B.new()) + Extend.InnerClass.test_c(C.new()) + Extend.InnerClass.InnerInnerClass.test_a_b_c(A.new(), B.new(), C.new()) + Extend.InnerClass.InnerInnerClass.test_enum(C.TestEnum.HELLO_WORLD) + Extend.InnerClass.InnerInnerClass.test_a_prime(A.APrime.new()) + diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.out b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.out new file mode 100644 index 0000000000..bd27bd31f6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.out @@ -0,0 +1,7 @@ +GDTEST_OK +true +true +true +true +true +true diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_a.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_a.notest.gd new file mode 100644 index 0000000000..966c8bfc8f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_a.notest.gd @@ -0,0 +1,2 @@ +class APrime: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_b.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_b.notest.gd new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_b.notest.gd diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_base.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_base.notest.gd new file mode 100644 index 0000000000..666b147ced --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_base.notest.gd @@ -0,0 +1,4 @@ +const A: = preload("base_outer_resolution_a.notest.gd") + +class InnerClassInBase: + const C: = preload("base_outer_resolution_c.notest.gd") diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_c.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_c.notest.gd new file mode 100644 index 0000000000..814be35314 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_c.notest.gd @@ -0,0 +1,3 @@ +enum TestEnum { + HELLO_WORLD +} diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_extend.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_extend.notest.gd new file mode 100644 index 0000000000..fbd28779d4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_extend.notest.gd @@ -0,0 +1,23 @@ +extends "base_outer_resolution_base.notest.gd" + +const B: = preload("base_outer_resolution_b.notest.gd") + +static func test_a(a: A) -> void: + print(a is A) + +static func test_b(b: B) -> void: + print(b is B) + +class InnerClass extends InnerClassInBase: + static func test_c(c: C) -> void: + print(c is C) + + class InnerInnerClass: + static func test_a_b_c(a: A, b: B, c: C) -> void: + print(a is A and b is B and c is C) + + static func test_enum(test_enum: C.TestEnum) -> void: + print(test_enum == C.TestEnum.HELLO_WORLD) + + static func test_a_prime(a_prime: A.APrime) -> void: + print(a_prime is A.APrime) diff --git a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd new file mode 100644 index 0000000000..5303fb04e2 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd @@ -0,0 +1,35 @@ +# https://github.com/godotengine/godot/issues/63965 + +func test(): + var array_str: Array = [] + array_str.push_back("godot") + print("StringName in Array: ", &"godot" in array_str) + + var array_sname: Array = [] + array_sname.push_back(&"godot") + print("String in Array: ", "godot" in array_sname) + + # Not equal because the values are different types. + print("Arrays not equal: ", array_str != array_sname) + + var string_array: Array[String] = [] + var stringname_array: Array[StringName] = [] + + assert(!string_array.push_back(&"abc")) + print("Array[String] insert converted: ", typeof(string_array[0]) == TYPE_STRING) + + assert(!stringname_array.push_back("abc")) + print("Array[StringName] insert converted: ", typeof(stringname_array[0]) == TYPE_STRING_NAME) + + print("StringName in Array[String]: ", &"abc" in string_array) + print("String in Array[StringName]: ", "abc" in stringname_array) + + var packed_string_array: PackedStringArray = [] + assert(!packed_string_array.push_back("abc")) + print("StringName in PackedStringArray: ", &"abc" in packed_string_array) + + assert(!string_array.push_back("abc")) + print("StringName finds String in Array: ", string_array.find(&"abc")) + + assert(!stringname_array.push_back(&"abc")) + print("String finds StringName in Array: ", stringname_array.find("abc")) diff --git a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.out new file mode 100644 index 0000000000..98ab78e8f1 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.out @@ -0,0 +1,11 @@ +GDTEST_OK +StringName in Array: true +String in Array: true +Arrays not equal: true +Array[String] insert converted: true +Array[StringName] insert converted: true +StringName in Array[String]: true +String in Array[StringName]: true +StringName in PackedStringArray: true +StringName finds String in Array: 0 +String finds StringName in Array: 0 diff --git a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd new file mode 100644 index 0000000000..1f15026f17 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd @@ -0,0 +1,17 @@ +# https://github.com/godotengine/godot/issues/62957 + +func test(): + var string_dict = {} + string_dict["abc"] = 42 + var stringname_dict = {} + stringname_dict[&"abc"] = 24 + + print("String key is TYPE_STRING: ", typeof(string_dict.keys()[0]) == TYPE_STRING) + print("StringName key is TYPE_STRING: ", typeof(stringname_dict.keys()[0]) == TYPE_STRING) + + print("StringName gets String: ", string_dict.get(&"abc")) + print("String gets StringName: ", stringname_dict.get("abc")) + + stringname_dict[&"abc"] = 42 + # They compare equal because StringName keys are converted to String. + print("String Dictionary == StringName Dictionary: ", string_dict == stringname_dict) diff --git a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out new file mode 100644 index 0000000000..ab5b89d55c --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out @@ -0,0 +1,6 @@ +GDTEST_OK +String key is TYPE_STRING: true +StringName key is TYPE_STRING: true +StringName gets String: 42 +String gets StringName: 24 +String Dictionary == StringName Dictionary: true diff --git a/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd new file mode 100644 index 0000000000..55be021a90 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd @@ -0,0 +1,14 @@ +# https://github.com/godotengine/godot/issues/60145 + +func test(): + match "abc": + &"abc": + print("String matched StringName") + _: + print("no match") + + match &"abc": + "abc": + print("StringName matched String") + _: + print("no match") diff --git a/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out new file mode 100644 index 0000000000..9d5a18da3d --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out @@ -0,0 +1,3 @@ +GDTEST_OK +String matched StringName +StringName matched String diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd new file mode 100644 index 0000000000..f33ba7dffd --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd @@ -0,0 +1,25 @@ +# https://github.com/godotengine/godot/pull/69620 + +var a: int = 1 + +func shadow_regular_assignment(a: Variant, b: Variant) -> void: + print(a) + print(self.a) + a = b + print(a) + print(self.a) + + +var v := Vector2(0.0, 0.0) + +func shadow_subscript_assignment(v: Vector2, x: float) -> void: + print(v) + print(self.v) + v.x += x + print(v) + print(self.v) + + +func test(): + shadow_regular_assignment('a', 'b') + shadow_subscript_assignment(Vector2(1.0, 1.0), 5.0) diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out new file mode 100644 index 0000000000..5b981bc8bb --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out @@ -0,0 +1,17 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> SHADOWED_VARIABLE +>> The local function parameter "a" is shadowing an already-declared variable at line 3. +>> WARNING +>> Line: 15 +>> SHADOWED_VARIABLE +>> The local function parameter "v" is shadowing an already-declared variable at line 13. +a +1 +b +1 +(1, 1) +(0, 0) +(6, 1) +(0, 0) diff --git a/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd new file mode 100644 index 0000000000..f8bd46523e --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd @@ -0,0 +1,11 @@ +# https://github.com/godotengine/godot/issues/64171 + +func test(): + print("Compare ==: ", "abc" == &"abc") + print("Compare ==: ", &"abc" == "abc") + print("Compare !=: ", "abc" != &"abc") + print("Compare !=: ", &"abc" != "abc") + + print("Concat: ", "abc" + &"def") + print("Concat: ", &"abc" + "def") + print("Concat: ", &"abc" + &"def") diff --git a/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out new file mode 100644 index 0000000000..7e9c364b60 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out @@ -0,0 +1,8 @@ +GDTEST_OK +Compare ==: true +Compare ==: true +Compare !=: false +Compare !=: false +Concat: abcdef +Concat: abcdef +Concat: abcdef |