summaryrefslogtreecommitdiffstats
path: root/modules/gdscript
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript')
-rw-r--r--modules/gdscript/gdscript.cpp19
-rw-r--r--modules/gdscript/gdscript.h2
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp4
-rw-r--r--modules/gdscript/gdscript_cache.cpp33
-rw-r--r--modules/gdscript/gdscript_cache.h2
-rw-r--r--modules/gdscript/gdscript_compiler.cpp39
-rw-r--r--modules/gdscript/gdscript_editor.cpp66
-rw-r--r--modules/gdscript/gdscript_parser.cpp2
-rw-r--r--modules/gdscript/gdscript_parser.h2
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp4
-rw-r--r--modules/gdscript/gdscript_vm.cpp4
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd14
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.gd9
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.gd20
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.out1
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd11
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.out1
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.gd62
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out26
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.gd13
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd15
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.out1
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