diff options
Diffstat (limited to 'modules/gdscript')
25 files changed, 310 insertions, 52 deletions
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 73b1e44db3..eaf2565e69 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1537,10 +1537,14 @@ void GDScript::clear(ClearData *p_clear_data) { } } - RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies(); - for (GDScript *E : must_clear_dependencies) { - clear_data->scripts.insert(E); - E->clear(clear_data); + // If we're in the process of shutting things down then every single script will be cleared + // anyway, so we can safely skip this very costly operation. + if (!GDScriptLanguage::singleton->finishing) { + RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies(); + for (GDScript *E : must_clear_dependencies) { + clear_data->scripts.insert(E); + E->clear(clear_data); + } } for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { @@ -2246,6 +2250,11 @@ String GDScriptLanguage::get_extension() const { } void GDScriptLanguage::finish() { + if (finishing) { + return; + } + finishing = true; + _call_stack.free(); // Clear the cache before parsing the script_list @@ -2281,6 +2290,8 @@ void GDScriptLanguage::finish() { } script_list.clear(); function_list.clear(); + + finishing = false; } void GDScriptLanguage::profiling_start() { diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index d097cb193b..4e78fbe302 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -411,6 +411,8 @@ class GDScriptLanguage : public ScriptLanguage { static GDScriptLanguage *singleton; + bool finishing = false; + Variant *_global_array = nullptr; Vector<Variant> global_array; HashMap<StringName, int> globals; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 67b40a6198..bfc78d2605 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -1967,8 +1967,8 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } #ifdef DEBUG_ENABLED + const bool is_parameter = p_assignable->type == GDScriptParser::Node::PARAMETER; if (!has_specified_type) { - const bool is_parameter = p_assignable->type == GDScriptParser::Node::PARAMETER; const String declaration_type = is_constant ? "Constant" : (is_parameter ? "Parameter" : "Variable"); if (p_assignable->infer_datatype || is_constant) { // Do not produce the `INFERRED_DECLARATION` warning on type import because there is no way to specify the true type. @@ -1980,7 +1980,7 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } else { parser->push_warning(p_assignable, GDScriptWarning::UNTYPED_DECLARATION, declaration_type, p_assignable->identifier->name); } - } else if (specified_type.kind == GDScriptParser::DataType::ENUM && p_assignable->initializer == nullptr) { + } else if (!is_parameter && specified_type.kind == GDScriptParser::DataType::ENUM && p_assignable->initializer == nullptr) { // Warn about enum variables without default value. Unless the enum defines the "0" value, then it's fine. bool has_zero_value = false; for (const KeyValue<StringName, int64_t> &kv : specified_type.enum_values) { diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 7c9fba799d..2b1184bcb9 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -135,7 +135,9 @@ void GDScriptParserRef::clear() { GDScriptParserRef::~GDScriptParserRef() { clear(); - GDScriptCache::remove_parser(path); + if (!abandoned) { + GDScriptCache::remove_parser(path); + } } GDScriptCache *GDScriptCache::singleton = nullptr; @@ -183,6 +185,17 @@ void GDScriptCache::remove_script(const String &p_path) { return; } + if (HashMap<String, Vector<ObjectID>>::Iterator E = singleton->abandoned_parser_map.find(p_path)) { + for (ObjectID parser_ref_id : E->value) { + Ref<GDScriptParserRef> parser_ref{ ObjectDB::get_instance(parser_ref_id) }; + if (parser_ref.is_valid()) { + parser_ref->clear(); + } + } + } + + singleton->abandoned_parser_map.erase(p_path); + if (singleton->parser_map.has(p_path)) { singleton->parser_map[p_path]->clear(); } @@ -229,6 +242,13 @@ bool GDScriptCache::has_parser(const String &p_path) { void GDScriptCache::remove_parser(const String &p_path) { MutexLock lock(singleton->mutex); + + if (singleton->parser_map.has(p_path)) { + GDScriptParserRef *parser_ref = singleton->parser_map[p_path]; + parser_ref->abandoned = true; + singleton->abandoned_parser_map[p_path].push_back(parser_ref->get_instance_id()); + } + // Can't clear the parser because some other parser might be currently using it in the chain of calls. singleton->parser_map.erase(p_path); @@ -432,6 +452,17 @@ void GDScriptCache::clear() { singleton->parser_inverse_dependencies.clear(); + for (const KeyValue<String, Vector<ObjectID>> &KV : singleton->abandoned_parser_map) { + for (ObjectID parser_ref_id : KV.value) { + Ref<GDScriptParserRef> parser_ref{ ObjectDB::get_instance(parser_ref_id) }; + if (parser_ref.is_valid()) { + parser_ref->clear(); + } + } + } + + singleton->abandoned_parser_map.clear(); + RBSet<Ref<GDScriptParserRef>> parser_map_refs; for (KeyValue<String, GDScriptParserRef *> &E : singleton->parser_map) { parser_map_refs.insert(E.value); diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index c927317e19..f7f2cd90e9 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -59,6 +59,7 @@ private: String path; uint32_t source_hash = 0; bool clearing = false; + bool abandoned = false; friend class GDScriptCache; friend class GDScript; @@ -79,6 +80,7 @@ public: class GDScriptCache { // String key is full path. HashMap<String, GDScriptParserRef *> parser_map; + HashMap<String, Vector<ObjectID>> abandoned_parser_map; HashMap<String, Ref<GDScript>> shallow_gdscript_cache; HashMap<String, Ref<GDScript>> full_gdscript_cache; HashMap<String, Ref<GDScript>> static_gdscript_cache; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 5469dad3f7..d8b44a558f 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1064,12 +1064,24 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Get at (potential) root stack pos, so it can be returned. GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, chain.back()->get()->base); + const bool base_known_type = base.type.has_type; + const bool base_is_shared = Variant::is_type_shared(base.type.builtin_type); + if (r_error) { return GDScriptCodeGenerator::Address(); } GDScriptCodeGenerator::Address prev_base = base; + // In case the base has a setter, don't use the address directly, as we want to call that setter. + // So use a temp value instead and call the setter at the end. + GDScriptCodeGenerator::Address base_temp; + if ((!base_known_type || !base_is_shared) && base.mode == GDScriptCodeGenerator::Address::MEMBER && member_property_has_setter && !member_property_is_in_setter) { + base_temp = codegen.add_temporary(base.type); + gen->write_assign(base_temp, base); + prev_base = base_temp; + } + struct ChainInfo { bool is_named = false; GDScriptCodeGenerator::Address base; @@ -1218,6 +1230,15 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code gen->write_end_jump_if_shared(); } } + } else if (base_temp.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + if (!base_known_type) { + gen->write_jump_if_shared(base); + } + // Save the temp value back to the base by calling its setter. + gen->write_call(GDScriptCodeGenerator::Address(), base, member_property_setter_function, { assigned }); + if (!base_known_type) { + gen->write_end_jump_if_shared(); + } } if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { @@ -1880,7 +1901,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui case GDScriptParser::Node::MATCH: { const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(s); - codegen.start_block(); // Add an extra block, since the binding pattern and @special variables belong to the branch scope. + codegen.start_block(); // Add an extra block, since @special locals belong to the match scope. // Evaluate the match expression. GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype(), codegen.script)); @@ -1918,7 +1939,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui const GDScriptParser::MatchBranchNode *branch = match->branches[j]; - codegen.start_block(); // Create an extra block around for binds. + codegen.start_block(); // Add an extra block, since binds belong to the match branch scope. // Add locals in block before patterns, so temporaries don't use the stack address for binds. List<GDScriptCodeGenerator::Address> branch_locals = _add_block_locals(codegen, branch->block); @@ -1970,13 +1991,15 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui _clear_block_locals(codegen, branch_locals); - codegen.end_block(); // Get out of extra block. + codegen.end_block(); // Get out of extra block for binds. } // End all nested `if`s. for (int j = 0; j < match->branches.size(); j++) { gen->write_endif(); } + + codegen.end_block(); // Get out of extra block for match's @special locals. } break; case GDScriptParser::Node::IF: { const GDScriptParser::IfNode *if_n = static_cast<const GDScriptParser::IfNode *>(s); @@ -2010,7 +2033,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui case GDScriptParser::Node::FOR: { const GDScriptParser::ForNode *for_n = static_cast<const GDScriptParser::ForNode *>(s); - codegen.start_block(); // Add an extra block, since the iterator and @special variables belong to the loop scope. + // Add an extra block, since the iterator and @special locals belong to the loop scope. + // Also we use custom logic to clear block locals. + codegen.start_block(); GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype(), codegen.script)); @@ -2043,11 +2068,13 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui _clear_block_locals(codegen, loop_locals); // Outside loop, after block - for `break` and normal exit. - codegen.end_block(); // Get out of extra block. + codegen.end_block(); // Get out of extra block for loop iterator, @special locals, and custom locals clearing. } break; case GDScriptParser::Node::WHILE: { const GDScriptParser::WhileNode *while_n = static_cast<const GDScriptParser::WhileNode *>(s); + codegen.start_block(); // Add an extra block, since we use custom logic to clear block locals. + gen->start_while_condition(); GDScriptCodeGenerator::Address condition = _parse_expression(codegen, err, while_n->condition); @@ -2074,6 +2101,8 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui gen->write_endwhile(); _clear_block_locals(codegen, loop_locals); // Outside loop, after block - for `break` and normal exit. + + codegen.end_block(); // Get out of extra block for custom locals clearing. } break; case GDScriptParser::Node::BREAK: { gen->write_break(); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index b58b44973e..822fc412b4 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -402,7 +402,9 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> } const Variant &var = gl_array[E.value]; - if (Object *obj = var) { + bool freed = false; + const Object *obj = var.get_validated_object_with_check(freed); + if (obj && !freed) { if (Object::cast_to<GDScriptNativeClass>(obj)) { continue; } @@ -796,7 +798,7 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio case GDScriptParser::Node::CALL: { const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(par->initializer); if (call->is_constant && call->reduced) { - def_val = call->function_name.operator String() + call->reduced_value.operator String(); + def_val = call->reduced_value.get_construct_string(); } else { def_val = call->function_name.operator String() + (call->arguments.is_empty() ? "()" : "(...)"); } @@ -804,7 +806,7 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio case GDScriptParser::Node::ARRAY: { const GDScriptParser::ArrayNode *arr = static_cast<const GDScriptParser::ArrayNode *>(par->initializer); if (arr->is_constant && arr->reduced) { - def_val = arr->reduced_value.operator String(); + def_val = arr->reduced_value.get_construct_string(); } else { def_val = arr->elements.is_empty() ? "[]" : "[...]"; } @@ -812,24 +814,17 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio case GDScriptParser::Node::DICTIONARY: { const GDScriptParser::DictionaryNode *dict = static_cast<const GDScriptParser::DictionaryNode *>(par->initializer); if (dict->is_constant && dict->reduced) { - def_val = dict->reduced_value.operator String(); + def_val = dict->reduced_value.get_construct_string(); } else { def_val = dict->elements.is_empty() ? "{}" : "{...}"; } } break; case GDScriptParser::Node::SUBSCRIPT: { const GDScriptParser::SubscriptNode *sub = static_cast<const GDScriptParser::SubscriptNode *>(par->initializer); - if (sub->is_constant) { - if (sub->datatype.kind == GDScriptParser::DataType::ENUM) { - def_val = sub->get_datatype().to_string(); - } else if (sub->reduced) { - const Variant::Type vt = sub->reduced_value.get_type(); - if (vt == Variant::Type::NIL || vt == Variant::Type::FLOAT || vt == Variant::Type::INT || vt == Variant::Type::STRING || vt == Variant::Type::STRING_NAME || vt == Variant::Type::BOOL || vt == Variant::Type::NODE_PATH) { - def_val = sub->reduced_value.operator String(); - } else { - def_val = sub->get_datatype().to_string() + sub->reduced_value.operator String(); - } - } + if (sub->is_attribute && sub->datatype.kind == GDScriptParser::DataType::ENUM && !sub->datatype.is_meta_type) { + def_val = sub->get_datatype().to_string() + "." + sub->attribute->name; + } else if (sub->is_constant && sub->reduced) { + def_val = sub->reduced_value.get_construct_string(); } } break; default: @@ -1521,22 +1516,19 @@ static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value, G } if (scr.is_valid()) { ci.type.script_path = scr->get_path(); + ci.type.script_type = scr; + ci.type.native_type = scr->get_instance_base_type(); + ci.type.kind = GDScriptParser::DataType::SCRIPT; if (scr->get_path().ends_with(".gd")) { - Error err; - Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(scr->get_path(), GDScriptParserRef::INTERFACE_SOLVED, err); - if (err == OK) { + Ref<GDScriptParserRef> parser = p_context.parser->get_depended_parser_for(scr->get_path()); + if (parser.is_valid() && parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED) == OK) { ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; ci.type.class_type = parser->get_parser()->get_tree(); ci.type.kind = GDScriptParser::DataType::CLASS; - p_context.dependent_parsers.push_back(parser); return ci; } } - - ci.type.kind = GDScriptParser::DataType::SCRIPT; - ci.type.script_type = scr; - ci.type.native_type = scr->get_instance_base_type(); } else { ci.type.kind = GDScriptParser::DataType::NATIVE; } @@ -1811,8 +1803,6 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, if (mb && mb->is_const()) { bool all_is_const = true; Vector<Variant> args; - GDScriptParser::CompletionContext c2 = p_context; - c2.current_line = call->start_line; for (int i = 0; all_is_const && i < call->arguments.size(); i++) { GDScriptCompletionIdentifier arg; @@ -1849,16 +1839,14 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, } if (FileAccess::exists(script)) { - Error err = OK; - Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(script, GDScriptParserRef::INTERFACE_SOLVED, err); - if (err == OK) { + Ref<GDScriptParserRef> parser = p_context.parser->get_depended_parser_for(script); + if (parser.is_valid() && parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED) == OK) { r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.script_path = script; r_type.type.class_type = parser->get_parser()->get_tree(); r_type.type.is_constant = false; r_type.type.kind = GDScriptParser::DataType::CLASS; r_type.value = Variant(); - p_context.dependent_parsers.push_back(parser); found = true; } } @@ -2310,9 +2298,8 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, if (ScriptServer::is_global_class(p_identifier->name)) { String script = ScriptServer::get_global_class_path(p_identifier->name); if (script.to_lower().ends_with(".gd")) { - Error err = OK; - Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(script, GDScriptParserRef::INTERFACE_SOLVED, err); - if (err == OK) { + Ref<GDScriptParserRef> parser = p_context.parser->get_depended_parser_for(script); + if (parser.is_valid() && parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED) == OK) { r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.script_path = script; r_type.type.class_type = parser->get_parser()->get_tree(); @@ -2320,7 +2307,6 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, r_type.type.is_constant = false; r_type.type.kind = GDScriptParser::DataType::CLASS; r_type.value = Variant(); - p_context.dependent_parsers.push_back(parser); return true; } } else { @@ -2768,6 +2754,20 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c base_type = base_type.class_type->base_type; } break; + case GDScriptParser::DataType::SCRIPT: { + if (base_type.script_type->is_valid() && base_type.script_type->has_method(p_method)) { + r_arghint = _make_arguments_hint(base_type.script_type->get_method_info(p_method), p_argidx); + return; + } + Ref<Script> base_script = base_type.script_type->get_base_script(); + if (base_script.is_valid()) { + base_type.script_type = base_script; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.builtin_type = Variant::OBJECT; + base_type.native_type = base_type.script_type->get_instance_base_type(); + } + } break; case GDScriptParser::DataType::NATIVE: { StringName class_name = base_type.native_type; if (!ClassDB::class_exists(class_name)) { diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 433f767f1e..54bb152f7f 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -260,6 +260,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node context.current_line = tokenizer->get_cursor_line(); context.current_argument = p_argument; context.node = p_node; + context.parser = this; completion_context = context; } @@ -277,6 +278,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Typ context.current_suite = current_suite; context.current_line = tokenizer->get_cursor_line(); context.builtin_type = p_builtin_type; + context.parser = this; completion_context = context; } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 21942222cf..60ee477656 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1314,7 +1314,7 @@ public: Variant::Type builtin_type = Variant::VARIANT_MAX; Node *node = nullptr; Object *base = nullptr; - List<Ref<GDScriptParserRef>> dependent_parsers; + GDScriptParser *parser = nullptr; }; struct CompletionCall { diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 5b1639e250..404b61fb40 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -574,7 +574,9 @@ GDScriptTokenizer::Token GDScriptTokenizerText::potential_identifier() { if (len == 1 && _peek(-1) == '_') { // Lone underscore. - return make_token(Token::UNDERSCORE); + Token token = make_token(Token::UNDERSCORE); + token.literal = "_"; + return token; } String name(_start, len); diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 912367764b..5eab8a6306 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -1807,7 +1807,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } else if (methodstr == "free") { if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { if (base->is_ref_counted()) { - err_text = "Attempted to free a reference."; + err_text = "Attempted to free a RefCounted object."; OPCODE_BREAK; } else if (base->get_type() == Variant::OBJECT) { err_text = "Attempted to free a locked object (calling or emitting)."; @@ -1898,7 +1898,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } else if (methodstr == "free") { if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { if (base->is_ref_counted()) { - err_text = "Attempted to free a reference."; + err_text = "Attempted to free a RefCounted object."; OPCODE_BREAK; } else if (base->get_type() == Variant::OBJECT) { err_text = "Attempted to free a locked object (calling or emitting)."; diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index fbfa4a0a79..025fcbd32a 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -622,7 +622,7 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { enable_stdout(); result.status = GDTEST_COMPILER_ERROR; result.output = get_text_for_status(result.status) + "\n"; - result.output = compiler.get_error(); + result.output += compiler.get_error() + "\n"; if (!p_is_generating) { result.passed = check_output(result.output); } diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd b/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd index 13e3edf93f..f3a8661acf 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd +++ b/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd @@ -7,3 +7,17 @@ var has_no_zero: HasNoZero # Warning, because there is no `0` in the enum. func test(): print(has_zero) print(has_no_zero) + + +# GH-94634. A parameter is either mandatory or has a default value. +func test_no_exec(param: HasNoZero) -> void: + print(param) + + # Loop iterator always has a value. + for i: HasNoZero in HasNoZero.values(): + print(i) + + match param: + # Pattern bind always has a value. + var x: + print(x) diff --git a/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.gd b/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.gd new file mode 100644 index 0000000000..3724c8c713 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.gd @@ -0,0 +1,9 @@ +# https://github.com/godotengine/godot/issues/90086 + +class MyObj: + var obj: WeakRef + +func test(): + var obj_1 = MyObj.new() + var obj_2 = MyObj.new() + obj_1.obj = obj_2 diff --git a/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.out b/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.out new file mode 100644 index 0000000000..dfca5b1eca --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/invalid_property_assignment.gd +>> 9 +>> Invalid assignment of property or key 'obj' with value of type 'RefCounted (MyObj)' on a base object of type 'RefCounted (MyObj)'. diff --git a/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.gd b/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.gd index c774ebf83c..df639a7b4d 100644 --- a/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.gd +++ b/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.gd @@ -1,6 +1,5 @@ # GH-77666 - -func test(): +func test_exit_if(): var ref := RefCounted.new() print(ref.get_reference_count()) @@ -8,3 +7,20 @@ func test(): var _temp := ref print(ref.get_reference_count()) + +# GH-94654 +func test_exit_while(): + var slots_data := [] + + while true: + @warning_ignore("confusable_local_declaration") + var slot = 42 + slots_data.append(slot) + break + + var slot: int = slots_data[0] + print(slot) + +func test(): + test_exit_if() + test_exit_while() diff --git a/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.out b/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.out index 04b4638adf..164eb24963 100644 --- a/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.out +++ b/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.out @@ -1,3 +1,4 @@ GDTEST_OK 1 1 +42 diff --git a/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd new file mode 100644 index 0000000000..e1aba83507 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd @@ -0,0 +1,11 @@ +# https://github.com/godotengine/godot/issues/90086 + +class MyObj: + var obj : WeakRef + +func test(): + var obj_1 = MyObj.new() + var obj_2 = MyObj.new() + assert(obj_2.get_reference_count() == 1) + obj_1.set(&"obj", obj_2) + assert(obj_2.get_reference_count() == 1) diff --git a/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.out b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.gd b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.gd new file mode 100644 index 0000000000..f70b521e1a --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.gd @@ -0,0 +1,62 @@ +# GH-94667 + +class Inner: + var subprop: Vector2: + set(value): + prints("subprop setter", value) + subprop = value + get: + print("subprop getter") + return subprop + + func _to_string() -> String: + return "<Inner>" + +var prop1: + set(value): + prints("prop1 setter", value) + prop1 = value + +var prop2: Inner: + set(value): + prints("prop2 setter", value) + prop2 = value + +var prop3: + set(value): + prints("prop3 setter", value) + prop3 = value + get: + print("prop3 getter") + return prop3 + +var prop4: Inner: + set(value): + prints("prop4 setter", value) + prop4 = value + get: + print("prop4 getter") + return prop4 + +func test(): + print("===") + prop1 = Vector2() + prop1.x = 1.0 + print("---") + prop1 = Inner.new() + prop1.subprop.x = 1.0 + + print("===") + prop2 = Inner.new() + prop2.subprop.x = 1.0 + + print("===") + prop3 = Vector2() + prop3.x = 1.0 + print("---") + prop3 = Inner.new() + prop3.subprop.x = 1.0 + + print("===") + prop4 = Inner.new() + prop4.subprop.x = 1.0 diff --git a/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out new file mode 100644 index 0000000000..c51759f481 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out @@ -0,0 +1,26 @@ +GDTEST_OK +=== +prop1 setter (0, 0) +prop1 setter (1, 0) +--- +prop1 setter <Inner> +subprop getter +subprop setter (1, 0) +=== +prop2 setter <Inner> +subprop getter +subprop setter (1, 0) +=== +prop3 setter (0, 0) +prop3 getter +prop3 setter (1, 0) +--- +prop3 setter <Inner> +prop3 getter +subprop getter +subprop setter (1, 0) +=== +prop4 setter <Inner> +prop4 getter +subprop getter +subprop setter (1, 0) diff --git a/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.gd b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.gd new file mode 100644 index 0000000000..9e27a500bf --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.gd @@ -0,0 +1,13 @@ +# https://github.com/godotengine/godot/issues/85952 + +var vec: Vector2 = Vector2.ZERO: + set(new_vec): + prints("setting vec from", vec, "to", new_vec) + if new_vec == Vector2(1, 1): + vec = new_vec + +func test(): + vec.x = 2 + vec.y = 2 + + prints("vec is", vec) diff --git a/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out new file mode 100644 index 0000000000..31b3b3a3a8 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out @@ -0,0 +1,4 @@ +GDTEST_OK +setting vec from (0, 0) to (2, 0) +setting vec from (0, 0) to (0, 2) +vec is (0, 0) diff --git a/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd new file mode 100644 index 0000000000..b07c40b6da --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd @@ -0,0 +1,15 @@ +extends Node + +func test() -> void: + var node1 := Node.new() + node1.name = "_" + var node2 := Node.new() + node2.name = "Child" + var node3 := Node.new() + node3.name = "Child" + + add_child(node1) + node1.add_child(node2) + add_child(node3) + + assert(get_node("_/Child") == $_/Child) diff --git a/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.out b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.out @@ -0,0 +1 @@ +GDTEST_OK |