summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanil Alexeev <danil@alexeev.xyz>2023-05-16 13:03:53 +0300
committerDanil Alexeev <danil@alexeev.xyz>2023-06-16 22:52:11 +0300
commitaebbbda08060e0cd130c5a682cd91b6babb18c67 (patch)
tree169fc0acf33dd216f4247bc02e8bc9e512864ac5
parent598378513b256e69e9b824c36136774c41cc763c (diff)
downloadredot-engine-aebbbda08060e0cd130c5a682cd91b6babb18c67.tar.gz
GDScript: Fix some bugs with static variables and functions
-rw-r--r--modules/gdscript/gdscript.cpp279
-rw-r--r--modules/gdscript/gdscript.h1
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp10
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp14
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h4
-rw-r--r--modules/gdscript/gdscript_codegen.h3
-rw-r--r--modules/gdscript/gdscript_compiler.cpp172
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp30
-rw-r--r--modules/gdscript/gdscript_function.h7
-rw-r--r--modules/gdscript/gdscript_parser.cpp10
-rw-r--r--modules/gdscript/gdscript_vm.cpp48
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.gd8
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.gd58
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.out25
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.gd17
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.out3
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_variables.gd18
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_variables.out18
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_variables_2.gd56
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_variables_2.out15
21 files changed, 614 insertions, 184 deletions
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index c3547e3db7..cb70c268b1 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -878,44 +878,55 @@ Variant GDScript::callp(const StringName &p_method, const Variant **p_args, int
}
bool GDScript::_get(const StringName &p_name, Variant &r_ret) const {
- {
- const GDScript *top = this;
- while (top) {
- {
- HashMap<StringName, Variant>::ConstIterator E = top->constants.find(p_name);
- if (E) {
- r_ret = E->value;
- return true;
- }
+ if (p_name == GDScriptLanguage::get_singleton()->strings._script_source) {
+ r_ret = get_source_code();
+ return true;
+ }
+
+ const GDScript *top = this;
+ while (top) {
+ {
+ HashMap<StringName, Variant>::ConstIterator E = top->constants.find(p_name);
+ if (E) {
+ r_ret = E->value;
+ return true;
}
+ }
- {
- HashMap<StringName, Ref<GDScript>>::ConstIterator E = subclasses.find(p_name);
- if (E) {
- r_ret = E->value;
+ {
+ HashMap<StringName, MemberInfo>::ConstIterator E = top->static_variables_indices.find(p_name);
+ if (E) {
+ if (E->value.getter) {
+ Callable::CallError ce;
+ r_ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce);
return true;
}
+ r_ret = top->static_variables[E->value.index];
+ return true;
}
+ }
- {
- HashMap<StringName, MemberInfo>::ConstIterator E = static_variables_indices.find(p_name);
- if (E) {
- if (E->value.getter) {
- Callable::CallError ce;
- r_ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce);
- return true;
- }
- r_ret = static_variables[E->value.index];
- return true;
+ {
+ HashMap<StringName, GDScriptFunction *>::ConstIterator E = top->member_functions.find(p_name);
+ if (E && E->value->is_static()) {
+ if (top->rpc_config.has(p_name)) {
+ r_ret = Callable(memnew(GDScriptRPCCallable(const_cast<GDScript *>(top), E->key)));
+ } else {
+ r_ret = Callable(const_cast<GDScript *>(top), E->key);
}
+ return true;
}
- top = top->_base;
}
- if (p_name == GDScriptLanguage::get_singleton()->strings._script_source) {
- r_ret = get_source_code();
- return true;
+ {
+ HashMap<StringName, Ref<GDScript>>::ConstIterator E = top->subclasses.find(p_name);
+ if (E) {
+ r_ret = E->value;
+ return true;
+ }
}
+
+ top = top->_base;
}
return false;
@@ -925,40 +936,60 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) {
if (p_name == GDScriptLanguage::get_singleton()->strings._script_source) {
set_source_code(p_value);
reload();
- } else {
- const GDScript *top = this;
- while (top) {
- HashMap<StringName, MemberInfo>::ConstIterator E = static_variables_indices.find(p_name);
- if (E) {
- const GDScript::MemberInfo *member = &E->value;
- Variant value = p_value;
- if (member->data_type.has_type && !member->data_type.is_type(value)) {
- const Variant *args = &p_value;
- Callable::CallError err;
- Variant::construct(member->data_type.builtin_type, value, &args, 1, err);
- if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) {
- return false;
- }
- }
- if (member->setter) {
- const Variant *args = &value;
- Callable::CallError err;
- callp(member->setter, &args, 1, err);
- return err.error == Callable::CallError::CALL_OK;
- } else {
- static_variables.write[member->index] = value;
- return true;
+ return true;
+ }
+
+ GDScript *top = this;
+ while (top) {
+ HashMap<StringName, MemberInfo>::ConstIterator E = top->static_variables_indices.find(p_name);
+ if (E) {
+ const MemberInfo *member = &E->value;
+ Variant value = p_value;
+ if (member->data_type.has_type && !member->data_type.is_type(value)) {
+ const Variant *args = &p_value;
+ Callable::CallError err;
+ Variant::construct(member->data_type.builtin_type, value, &args, 1, err);
+ if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) {
+ return false;
}
}
- top = top->_base;
+ if (member->setter) {
+ const Variant *args = &value;
+ Callable::CallError err;
+ callp(member->setter, &args, 1, err);
+ return err.error == Callable::CallError::CALL_OK;
+ } else {
+ top->static_variables.write[member->index] = value;
+ return true;
+ }
}
+
+ top = top->_base;
}
- return true;
+ return false;
}
void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const {
p_properties->push_back(PropertyInfo(Variant::STRING, "script/source", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+
+ List<PropertyInfo> property_list;
+
+ const GDScript *top = this;
+ while (top) {
+ for (const KeyValue<StringName, MemberInfo> &E : top->static_variables_indices) {
+ PropertyInfo pi = PropertyInfo(E.value.data_type);
+ pi.name = E.key;
+ pi.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; // For the script (as a class) it is a non-static property.
+ property_list.push_back(pi);
+ }
+
+ top = top->_base;
+ }
+
+ for (const List<PropertyInfo>::Element *E = property_list.back(); E; E = E->prev()) {
+ p_properties->push_back(E->get());
+ }
}
void GDScript::_bind_methods() {
@@ -1037,6 +1068,16 @@ StringName GDScript::debug_get_member_by_index(int p_idx) const {
return "<error>";
}
+StringName GDScript::debug_get_static_var_by_index(int p_idx) const {
+ for (const KeyValue<StringName, MemberInfo> &E : static_variables_indices) {
+ if (E.value.index == p_idx) {
+ return E.key;
+ }
+ }
+
+ return "<error>";
+}
+
Ref<GDScript> GDScript::get_base() const {
return base;
}
@@ -1376,8 +1417,8 @@ void GDScript::clear(GDScript::ClearData *p_clear_data) {
}
clearing = true;
- GDScript::ClearData data;
- GDScript::ClearData *clear_data = p_clear_data;
+ ClearData data;
+ ClearData *clear_data = p_clear_data;
bool is_root = false;
// If `clear_data` is `nullptr`, it means that it's the root.
@@ -1398,12 +1439,12 @@ void GDScript::clear(GDScript::ClearData *p_clear_data) {
}
member_functions.clear();
- for (KeyValue<StringName, GDScript::MemberInfo> &E : member_indices) {
+ for (KeyValue<StringName, MemberInfo> &E : member_indices) {
clear_data->scripts.insert(E.value.data_type.script_type_ref);
E.value.data_type.script_type_ref = Ref<Script>();
}
- for (KeyValue<StringName, GDScript::MemberInfo> &E : static_variables_indices) {
+ for (KeyValue<StringName, MemberInfo> &E : static_variables_indices) {
clear_data->scripts.insert(E.value.data_type.script_type_ref);
E.value.data_type.script_type_ref = Ref<Script>();
}
@@ -1486,7 +1527,6 @@ GDScript::~GDScript() {
//////////////////////////////
bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
- //member
{
HashMap<StringName, GDScript::MemberInfo>::Iterator E = script->member_indices.find(p_name);
if (E) {
@@ -1514,17 +1554,45 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
GDScript *sptr = script.ptr();
while (sptr) {
- HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._set);
- if (E) {
- Variant name = p_name;
- const Variant *args[2] = { &name, &p_value };
+ {
+ HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = sptr->static_variables_indices.find(p_name);
+ if (E) {
+ const GDScript::MemberInfo *member = &E->value;
+ Variant value = p_value;
+ if (member->data_type.has_type && !member->data_type.is_type(value)) {
+ const Variant *args = &p_value;
+ Callable::CallError err;
+ Variant::construct(member->data_type.builtin_type, value, &args, 1, err);
+ if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) {
+ return false;
+ }
+ }
+ if (member->setter) {
+ const Variant *args = &value;
+ Callable::CallError err;
+ callp(member->setter, &args, 1, err);
+ return err.error == Callable::CallError::CALL_OK;
+ } else {
+ sptr->static_variables.write[member->index] = value;
+ return true;
+ }
+ }
+ }
- Callable::CallError err;
- Variant ret = E->value->call(this, (const Variant **)args, 2, err);
- if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) {
- return true;
+ {
+ HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._set);
+ if (E) {
+ Variant name = p_name;
+ const Variant *args[2] = { &name, &p_value };
+
+ Callable::CallError err;
+ Variant ret = E->value->call(this, (const Variant **)args, 2, err);
+ if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) {
+ return true;
+ }
}
}
+
sptr = sptr->_base;
}
@@ -1532,62 +1600,69 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
}
bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
+ {
+ HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = script->member_indices.find(p_name);
+ if (E) {
+ if (E->value.getter) {
+ Callable::CallError err;
+ r_ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err);
+ if (err.error == Callable::CallError::CALL_OK) {
+ return true;
+ }
+ }
+ r_ret = members[E->value.index];
+ return true;
+ }
+ }
+
const GDScript *sptr = script.ptr();
while (sptr) {
{
- HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = script->member_indices.find(p_name);
+ HashMap<StringName, Variant>::ConstIterator E = sptr->constants.find(p_name);
+ if (E) {
+ r_ret = E->value;
+ return true;
+ }
+ }
+
+ {
+ HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = sptr->static_variables_indices.find(p_name);
if (E) {
if (E->value.getter) {
- Callable::CallError err;
- r_ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err);
- if (err.error == Callable::CallError::CALL_OK) {
- return true;
- }
+ Callable::CallError ce;
+ r_ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce);
+ return true;
}
- r_ret = members[E->value.index];
- return true; //index found
+ r_ret = sptr->static_variables[E->value.index];
+ return true;
}
}
{
- const GDScript *sl = sptr;
- while (sl) {
- HashMap<StringName, Variant>::ConstIterator E = sl->constants.find(p_name);
- if (E) {
- r_ret = E->value;
- return true; //index found
- }
- sl = sl->_base;
+ HashMap<StringName, Vector<StringName>>::ConstIterator E = sptr->_signals.find(p_name);
+ if (E) {
+ r_ret = Signal(this->owner, E->key);
+ return true;
}
}
{
- // Signals.
- const GDScript *sl = sptr;
- while (sl) {
- HashMap<StringName, Vector<StringName>>::ConstIterator E = sl->_signals.find(p_name);
- if (E) {
- r_ret = Signal(this->owner, E->key);
- return true; //index found
+ HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(p_name);
+ if (E) {
+ if (sptr->rpc_config.has(p_name)) {
+ r_ret = Callable(memnew(GDScriptRPCCallable(this->owner, E->key)));
+ } else {
+ r_ret = Callable(this->owner, E->key);
}
- sl = sl->_base;
+ return true;
}
}
{
- // Methods.
- const GDScript *sl = sptr;
- while (sl) {
- HashMap<StringName, GDScriptFunction *>::ConstIterator E = sl->member_functions.find(p_name);
- if (E) {
- if (sptr->rpc_config.has(p_name)) {
- r_ret = Callable(memnew(GDScriptRPCCallable(this->owner, E->key)));
- } else {
- r_ret = Callable(this->owner, E->key);
- }
- return true; //index found
- }
- sl = sl->_base;
+ HashMap<StringName, Ref<GDScript>>::ConstIterator E = sptr->subclasses.find(p_name);
+ if (E) {
+ r_ret = E->value;
+ return true;
}
}
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 1d6ff6406a..9cf545c41d 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -227,6 +227,7 @@ public:
const HashMap<StringName, MemberInfo> &debug_get_member_indices() const { return member_indices; }
const HashMap<StringName, GDScriptFunction *> &debug_get_member_functions() const; //this is debug only
StringName debug_get_member_by_index(int p_idx) const;
+ StringName debug_get_static_var_by_index(int p_idx) const;
Variant _new(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
virtual bool can_instantiate() const override;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index e3d3c44dd1..d72b18a95a 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -2469,9 +2469,15 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype();
- if (assignee_type.is_constant || (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->is_constant)) {
+ if (assignee_type.is_constant) {
push_error("Cannot assign a new value to a constant.", p_assignment->assignee);
return;
+ } else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->is_constant) {
+ const GDScriptParser::DataType &base_type = static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->datatype;
+ if (base_type.kind != GDScriptParser::DataType::SCRIPT && base_type.kind != GDScriptParser::DataType::CLASS) { // Static variables.
+ push_error("Cannot assign a new value to a constant.", p_assignment->assignee);
+ return;
+ }
} else if (assignee_type.is_read_only) {
push_error("Cannot assign a new value to a read-only property.", p_assignment->assignee);
return;
@@ -3516,7 +3522,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
} break;
case GDScriptParser::ClassNode::Member::FUNCTION: {
- if (is_base && !base.is_meta_type) {
+ if (is_base && (!base.is_meta_type || member.function->is_static)) {
p_identifier->set_datatype(make_callable_type(member.function->info));
return;
}
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 3239f64cb2..47cd3f768b 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -853,6 +853,20 @@ void GDScriptByteCodeGenerator::write_get_member(const Address &p_target, const
append(p_name);
}
+void GDScriptByteCodeGenerator::write_set_static_variable(const Address &p_value, const Address &p_class, int p_index) {
+ append_opcode(GDScriptFunction::OPCODE_SET_STATIC_VARIABLE);
+ append(p_value);
+ append(p_class);
+ append(p_index);
+}
+
+void GDScriptByteCodeGenerator::write_get_static_variable(const Address &p_target, const Address &p_class, int p_index) {
+ append_opcode(GDScriptFunction::OPCODE_GET_STATIC_VARIABLE);
+ append(p_target);
+ append(p_class);
+ append(p_index);
+}
+
void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_target, const Address &p_source) {
switch (p_target.type.kind) {
case GDScriptDataType::BUILTIN: {
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index bed5cc2405..bbcd252b13 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -365,8 +365,6 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
return p_address.address | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS);
case Address::CONSTANT:
return p_address.address | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS);
- case Address::STATIC_VARIABLE:
- return p_address.address | (GDScriptFunction::ADDR_TYPE_STATIC_VAR << GDScriptFunction::ADDR_BITS);
case Address::LOCAL_VARIABLE:
case Address::FUNCTION_PARAMETER:
return p_address.address | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
@@ -502,6 +500,8 @@ public:
virtual void write_get_named(const Address &p_target, const StringName &p_name, const Address &p_source) override;
virtual void write_set_member(const Address &p_value, const StringName &p_name) override;
virtual void write_get_member(const Address &p_target, const StringName &p_name) override;
+ virtual void write_set_static_variable(const Address &p_value, const Address &p_class, int p_index) override;
+ virtual void write_get_static_variable(const Address &p_target, const Address &p_class, int p_index) override;
virtual void write_assign(const Address &p_target, const Address &p_source) override;
virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) override;
virtual void write_assign_true(const Address &p_target) override;
diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h
index fa1732d58f..9810f5395a 100644
--- a/modules/gdscript/gdscript_codegen.h
+++ b/modules/gdscript/gdscript_codegen.h
@@ -45,7 +45,6 @@ public:
CLASS,
MEMBER,
CONSTANT,
- STATIC_VARIABLE,
LOCAL_VARIABLE,
FUNCTION_PARAMETER,
TEMPORARY,
@@ -111,6 +110,8 @@ public:
virtual void write_get_named(const Address &p_target, const StringName &p_name, const Address &p_source) = 0;
virtual void write_set_member(const Address &p_value, const StringName &p_name) = 0;
virtual void write_get_member(const Address &p_target, const StringName &p_name) = 0;
+ virtual void write_set_static_variable(const Address &p_value, const Address &p_class, int p_index) = 0;
+ virtual void write_get_static_variable(const Address &p_target, const Address &p_class, int p_index) = 0;
virtual void write_assign(const Address &p_target, const Address &p_source) = 0;
virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) = 0;
virtual void write_assign_true(const Address &p_target) = 0;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 327e24ef11..88470c30f4 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -262,18 +262,27 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
}
// Try static variables.
- if (codegen.script->static_variables_indices.has(identifier)) {
- if (codegen.script->static_variables_indices[identifier].getter != StringName() && codegen.script->static_variables_indices[identifier].getter != codegen.function_name) {
- // Perform getter.
- GDScriptCodeGenerator::Address temp = codegen.add_temporary(codegen.script->static_variables_indices[identifier].data_type);
- GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS);
- Vector<GDScriptCodeGenerator::Address> args; // No argument needed.
- gen->write_call(temp, class_addr, codegen.script->static_variables_indices[identifier].getter, args);
- return temp;
- } else {
- // No getter or inside getter: direct variable access.
- int idx = codegen.script->static_variables_indices[identifier].index;
- return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::STATIC_VARIABLE, idx, codegen.script->static_variables_indices[identifier].data_type);
+ {
+ GDScript *scr = codegen.script;
+ while (scr) {
+ if (scr->static_variables_indices.has(identifier)) {
+ if (scr->static_variables_indices[identifier].getter != StringName() && scr->static_variables_indices[identifier].getter != codegen.function_name) {
+ // Perform getter.
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(scr->static_variables_indices[identifier].data_type);
+ GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS);
+ Vector<GDScriptCodeGenerator::Address> args; // No argument needed.
+ gen->write_call(temp, class_addr, scr->static_variables_indices[identifier].getter, args);
+ return temp;
+ } else {
+ // No getter or inside getter: direct variable access.
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(scr->static_variables_indices[identifier].data_type);
+ GDScriptCodeGenerator::Address _class = codegen.add_constant(scr);
+ int index = scr->static_variables_indices[identifier].index;
+ gen->write_get_static_variable(temp, _class, index);
+ return temp;
+ }
+ }
+ scr = scr->_base;
}
}
@@ -926,6 +935,10 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
bool member_property_has_setter = false;
bool member_property_is_in_setter = false;
bool is_static = false;
+ GDScriptCodeGenerator::Address static_var_class;
+ int static_var_index = 0;
+ GDScriptDataType static_var_data_type;
+ StringName var_name;
StringName member_property_setter_function;
List<const GDScriptParser::SubscriptNode *> chain;
@@ -939,19 +952,39 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// Check for a property.
if (n->base->type == GDScriptParser::Node::IDENTIFIER) {
GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(n->base);
- StringName var_name = identifier->name;
+ var_name = identifier->name;
if (_is_class_member_property(codegen, var_name)) {
assign_class_member_property = var_name;
- } else if (!_is_local_or_parameter(codegen, var_name) && (codegen.script->member_indices.has(var_name) || codegen.script->static_variables_indices.has(var_name))) {
- is_member_property = true;
- is_static = codegen.script->static_variables_indices.has(var_name);
- const GDScript::MemberInfo &minfo = is_static ? codegen.script->static_variables_indices[var_name] : codegen.script->member_indices[var_name];
- member_property_setter_function = minfo.setter;
- member_property_has_setter = member_property_setter_function != StringName();
- member_property_is_in_setter = member_property_has_setter && member_property_setter_function == codegen.function_name;
- target_member_property.mode = is_static ? GDScriptCodeGenerator::Address::STATIC_VARIABLE : GDScriptCodeGenerator::Address::MEMBER;
- target_member_property.address = minfo.index;
- target_member_property.type = minfo.data_type;
+ } else if (!_is_local_or_parameter(codegen, var_name)) {
+ if (codegen.script->member_indices.has(var_name)) {
+ is_member_property = true;
+ is_static = false;
+ const GDScript::MemberInfo &minfo = codegen.script->member_indices[var_name];
+ member_property_setter_function = minfo.setter;
+ member_property_has_setter = member_property_setter_function != StringName();
+ member_property_is_in_setter = member_property_has_setter && member_property_setter_function == codegen.function_name;
+ target_member_property.mode = GDScriptCodeGenerator::Address::MEMBER;
+ target_member_property.address = minfo.index;
+ target_member_property.type = minfo.data_type;
+ } else {
+ // Try static variables.
+ GDScript *scr = codegen.script;
+ while (scr) {
+ if (scr->static_variables_indices.has(var_name)) {
+ is_member_property = true;
+ is_static = true;
+ const GDScript::MemberInfo &minfo = scr->static_variables_indices[var_name];
+ member_property_setter_function = minfo.setter;
+ member_property_has_setter = member_property_setter_function != StringName();
+ member_property_is_in_setter = member_property_has_setter && member_property_setter_function == codegen.function_name;
+ static_var_class = codegen.add_constant(scr);
+ static_var_index = minfo.index;
+ static_var_data_type = minfo.data_type;
+ break;
+ }
+ scr = scr->_base;
+ }
+ }
}
}
break;
@@ -1104,8 +1137,13 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
if (member_property_has_setter && !member_property_is_in_setter) {
Vector<GDScriptCodeGenerator::Address> args;
args.push_back(assigned);
- GDScriptCodeGenerator::Address self = is_static ? GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS) : GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF);
- gen->write_call(GDScriptCodeGenerator::Address(), self, member_property_setter_function, args);
+ GDScriptCodeGenerator::Address call_base = is_static ? GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS) : GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF);
+ gen->write_call(GDScriptCodeGenerator::Address(), call_base, member_property_setter_function, args);
+ } else if (is_static) {
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(static_var_data_type);
+ gen->write_assign(temp, assigned);
+ gen->write_set_static_variable(temp, static_var_class, static_var_index);
+ gen->pop_temporary();
} else {
gen->write_assign(target_member_property, assigned);
}
@@ -1155,18 +1193,42 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
bool has_setter = false;
bool is_in_setter = false;
bool is_static = false;
+ GDScriptCodeGenerator::Address static_var_class;
+ int static_var_index = 0;
+ GDScriptDataType static_var_data_type;
+ StringName var_name;
StringName setter_function;
- StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name;
- if (!_is_local_or_parameter(codegen, var_name) && (codegen.script->member_indices.has(var_name) || codegen.script->static_variables_indices.has(var_name))) {
- is_member = true;
- is_static = codegen.script->static_variables_indices.has(var_name);
- GDScript::MemberInfo &minfo = is_static ? codegen.script->static_variables_indices[var_name] : codegen.script->member_indices[var_name];
- setter_function = minfo.setter;
- has_setter = setter_function != StringName();
- is_in_setter = has_setter && setter_function == codegen.function_name;
- member.mode = is_static ? GDScriptCodeGenerator::Address::STATIC_VARIABLE : GDScriptCodeGenerator::Address::MEMBER;
- member.address = minfo.index;
- member.type = minfo.data_type;
+ var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name;
+ if (!_is_local_or_parameter(codegen, var_name)) {
+ if (codegen.script->member_indices.has(var_name)) {
+ is_member = true;
+ is_static = false;
+ GDScript::MemberInfo &minfo = codegen.script->member_indices[var_name];
+ setter_function = minfo.setter;
+ has_setter = setter_function != StringName();
+ is_in_setter = has_setter && setter_function == codegen.function_name;
+ member.mode = GDScriptCodeGenerator::Address::MEMBER;
+ member.address = minfo.index;
+ member.type = minfo.data_type;
+ } else {
+ // Try static variables.
+ GDScript *scr = codegen.script;
+ while (scr) {
+ if (scr->static_variables_indices.has(var_name)) {
+ is_member = true;
+ is_static = true;
+ GDScript::MemberInfo &minfo = scr->static_variables_indices[var_name];
+ setter_function = minfo.setter;
+ has_setter = setter_function != StringName();
+ is_in_setter = has_setter && setter_function == codegen.function_name;
+ static_var_class = codegen.add_constant(scr);
+ static_var_index = minfo.index;
+ static_var_data_type = minfo.data_type;
+ break;
+ }
+ scr = scr->_base;
+ }
+ }
}
GDScriptCodeGenerator::Address target;
@@ -1200,13 +1262,21 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
to_assign = assigned_value;
}
- GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype(), codegen.script);
-
if (has_setter && !is_in_setter) {
// Call setter.
Vector<GDScriptCodeGenerator::Address> args;
args.push_back(to_assign);
- gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), setter_function, args);
+ GDScriptCodeGenerator::Address call_base = is_static ? GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS) : GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF);
+ gen->write_call(GDScriptCodeGenerator::Address(), call_base, setter_function, args);
+ } else if (is_static) {
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(static_var_data_type);
+ if (assignment->use_conversion_assign) {
+ gen->write_assign_with_conversion(temp, to_assign);
+ } else {
+ gen->write_assign(temp, to_assign);
+ }
+ gen->write_set_static_variable(temp, static_var_class, static_var_index);
+ gen->pop_temporary();
} else {
// Just assign.
if (assignment->use_conversion_assign) {
@@ -2062,11 +2132,11 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
}
GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
- GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
-
if (field_type.has_type) {
codegen.generator->write_newline(field->start_line);
+ GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
+
if (field_type.has_container_element_type()) {
codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>());
} else if (field_type.kind == GDScriptDataType::BUILTIN) {
@@ -2093,9 +2163,6 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
continue;
}
- GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
-
- GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
if (field->initializer) {
// Emit proper line change.
codegen.generator->write_newline(field->initializer->start_line);
@@ -2106,6 +2173,9 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
return nullptr;
}
+ GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
+ GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
+
if (field->use_conversion_assign) {
codegen.generator->write_assign_with_conversion(dst_address, src_address);
} else {
@@ -2235,6 +2305,8 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
codegen.is_static = is_static;
codegen.generator->write_start(p_script, func_name, is_static, rpc_config, return_type);
+ // The static initializer is always called on the same class where the static variables are defined,
+ // so the CLASS address (current class) can be used instead of `codegen.add_constant(p_script)`.
GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS);
// Initialize the default values for typed variables before anything.
@@ -2251,20 +2323,18 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
}
GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
-
if (field_type.has_type) {
codegen.generator->write_newline(field->start_line);
if (field_type.has_container_element_type()) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
codegen.generator->write_construct_typed_array(temp, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>());
- codegen.generator->write_set_named(class_addr, field->identifier->name, temp);
+ codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index);
codegen.generator->pop_temporary();
-
} else if (field_type.kind == GDScriptDataType::BUILTIN) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
codegen.generator->write_construct(temp, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
- codegen.generator->write_set_named(class_addr, field->identifier->name, temp);
+ codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index);
codegen.generator->pop_temporary();
}
// The `else` branch is for objects, in such case we leave it as `null`.
@@ -2281,8 +2351,6 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
continue;
}
- GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
-
if (field->initializer) {
// Emit proper line change.
codegen.generator->write_newline(field->initializer->start_line);
@@ -2293,7 +2361,9 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
return nullptr;
}
+ GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
+
if (field->use_conversion_assign) {
codegen.generator->write_assign_with_conversion(temp, src_address);
} else {
@@ -2303,7 +2373,7 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
codegen.generator->pop_temporary();
}
- codegen.generator->write_set_named(class_addr, field->identifier->name, temp);
+ codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index);
codegen.generator->pop_temporary();
}
}
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
index cf33f12e3a..0438bd8903 100644
--- a/modules/gdscript/gdscript_disassembler.cpp
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -312,6 +312,36 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 3;
} break;
+ case OPCODE_SET_STATIC_VARIABLE: {
+ text += "set_static_variable script(";
+ Ref<GDScript> gdscript = get_constant(_code_ptr[ip + 2] & GDScriptFunction::ADDR_MASK);
+ text += gdscript.is_valid() ? gdscript->get_fully_qualified_name().get_file() : "<unknown script>";
+ text += ")";
+ if (gdscript.is_valid()) {
+ text += "[\"" + gdscript->debug_get_static_var_by_index(_code_ptr[ip + 3]) + "\"]";
+ } else {
+ text += "[<index " + itos(_code_ptr[ip + 3]) + ">]";
+ }
+ text += " = ";
+ text += DADDR(1);
+
+ incr += 4;
+ } break;
+ case OPCODE_GET_STATIC_VARIABLE: {
+ text += "get_static_variable ";
+ text += DADDR(1);
+ text += " = script(";
+ Ref<GDScript> gdscript = get_constant(_code_ptr[ip + 2] & GDScriptFunction::ADDR_MASK);
+ text += gdscript.is_valid() ? gdscript->get_fully_qualified_name().get_file() : "<unknown script>";
+ text += ")";
+ if (gdscript.is_valid()) {
+ text += "[\"" + gdscript->debug_get_static_var_by_index(_code_ptr[ip + 3]) + "\"]";
+ } else {
+ text += "[<index " + itos(_code_ptr[ip + 3]) + ">]";
+ }
+
+ incr += 4;
+ } break;
case OPCODE_ASSIGN: {
text += "assign ";
text += DADDR(1);
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index b6f3e7cc87..9bbfb14f31 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -149,6 +149,7 @@ public:
operator PropertyInfo() const {
PropertyInfo info;
+ info.usage = PROPERTY_USAGE_NONE;
if (has_type) {
switch (kind) {
case UNINITIALIZED:
@@ -238,6 +239,8 @@ public:
OPCODE_GET_NAMED_VALIDATED,
OPCODE_SET_MEMBER,
OPCODE_GET_MEMBER,
+ OPCODE_SET_STATIC_VARIABLE, // Only for GDScript.
+ OPCODE_GET_STATIC_VARIABLE, // Only for GDScript.
OPCODE_ASSIGN,
OPCODE_ASSIGN_TRUE,
OPCODE_ASSIGN_FALSE,
@@ -410,14 +413,14 @@ public:
ADDR_TYPE_STACK = 0,
ADDR_TYPE_CONSTANT = 1,
ADDR_TYPE_MEMBER = 2,
- ADDR_TYPE_STATIC_VAR = 3,
- ADDR_TYPE_MAX = 4,
+ ADDR_TYPE_MAX = 3,
};
enum FixedAddresses {
ADDR_STACK_SELF = 0,
ADDR_STACK_CLASS = 1,
ADDR_STACK_NIL = 2,
+ FIXED_ADDRESSES_MAX = 3,
ADDR_SELF = ADDR_STACK_SELF | (ADDR_TYPE_STACK << ADDR_BITS),
ADDR_CLASS = ADDR_STACK_CLASS | (ADDR_TYPE_STACK << ADDR_BITS),
ADDR_NIL = ADDR_STACK_NIL | (ADDR_TYPE_STACK << ADDR_BITS),
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index de8a5c66f4..962255f73c 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -3827,8 +3827,12 @@ bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node
}
VariableNode *variable = static_cast<VariableNode *>(p_node);
+ if (variable->is_static) {
+ push_error(R"("@onready" annotation cannot be applied to a static variable.)", p_annotation);
+ return false;
+ }
if (variable->onready) {
- push_error(R"("@onready" annotation can only be used once per variable.)");
+ push_error(R"("@onready" annotation can only be used once per variable.)", p_annotation);
return false;
}
variable->onready = true;
@@ -3841,6 +3845,10 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
VariableNode *variable = static_cast<VariableNode *>(p_node);
+ if (variable->is_static) {
+ push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation);
+ return false;
+ }
if (variable->exported) {
push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation);
return false;
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index 0ffc025c24..4545689bd9 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -217,6 +217,8 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_GET_NAMED_VALIDATED, \
&&OPCODE_SET_MEMBER, \
&&OPCODE_GET_MEMBER, \
+ &&OPCODE_SET_STATIC_VARIABLE, \
+ &&OPCODE_GET_STATIC_VARIABLE, \
&&OPCODE_ASSIGN, \
&&OPCODE_ASSIGN_TRUE, \
&&OPCODE_ASSIGN_FALSE, \
@@ -666,7 +668,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
Variant *m_v = instruction_args[m_idx]
#ifdef DEBUG_ENABLED
-
uint64_t function_start_time = 0;
uint64_t function_call_time = 0;
@@ -679,11 +680,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
bool exit_ok = false;
bool awaited = false;
#endif
+
#ifdef DEBUG_ENABLED
- int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0, script->static_variables.size() };
+ int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0 };
#endif
- Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr, script->static_variables.ptrw() };
+ Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr };
#ifdef DEBUG_ENABLED
OPCODE_WHILE(ip < _code_size) {
@@ -1171,6 +1173,42 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
+ OPCODE(OPCODE_SET_STATIC_VARIABLE) {
+ CHECK_SPACE(4);
+
+ GET_VARIANT_PTR(value, 0);
+
+ GET_VARIANT_PTR(_class, 1);
+ GDScript *gdscript = Object::cast_to<GDScript>(_class->operator Object *());
+ GD_ERR_BREAK(!gdscript);
+
+ int index = _code_ptr[ip + 3];
+ GD_ERR_BREAK(index < 0 || index >= gdscript->static_variables.size());
+
+ gdscript->static_variables.write[index] = *value;
+
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_GET_STATIC_VARIABLE) {
+ CHECK_SPACE(4);
+
+ GET_VARIANT_PTR(target, 0);
+
+ GET_VARIANT_PTR(_class, 1);
+ GDScript *gdscript = Object::cast_to<GDScript>(_class->operator Object *());
+ GD_ERR_BREAK(!gdscript);
+
+ int index = _code_ptr[ip + 3];
+ GD_ERR_BREAK(index < 0 || index >= gdscript->static_variables.size());
+
+ *target = gdscript->static_variables[index];
+
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
OPCODE(OPCODE_ASSIGN) {
CHECK_SPACE(3);
GET_VARIANT_PTR(dst, 0);
@@ -3620,7 +3658,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#endif
// Free stack, except reserved addresses.
- for (int i = 3; i < _stack_size; i++) {
+ for (int i = FIXED_ADDRESSES_MAX; i < _stack_size; i++) {
stack[i].~Variant();
}
#ifdef DEBUG_ENABLED
@@ -3628,7 +3666,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#endif
// Always free reserved addresses, since they are never copied.
- for (int i = 0; i < 3; i++) {
+ for (int i = 0; i < FIXED_ADDRESSES_MAX; i++) {
stack[i].~Variant();
}
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.gd
new file mode 100644
index 0000000000..66697cbb29
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.gd
@@ -0,0 +1,8 @@
+# GH-77098 p.3
+
+@static_unload
+
+@export static var a: int
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.out b/modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.out
new file mode 100644
index 0000000000..4111aa07af
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Annotation "@export" cannot be applied to a static variable.
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.gd b/modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.gd
new file mode 100644
index 0000000000..77d01cf00c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.gd
@@ -0,0 +1,58 @@
+# GH-77098 p.4
+
+@static_unload
+
+class A:
+ class InnerClass:
+ pass
+
+ enum NamedEnum { VALUE = 111 }
+ enum { UNNAMED_ENUM_VALUE = 222 }
+ const CONSTANT = 333
+ static var static_var := 1
+
+ static func static_func() -> int:
+ return 444
+
+class B extends A:
+ func test_self():
+ print(self.InnerClass is GDScript)
+ print(self.NamedEnum)
+ print(self.NamedEnum.VALUE)
+ print(self.UNNAMED_ENUM_VALUE)
+ print(self.CONSTANT)
+ @warning_ignore("static_called_on_instance")
+ print(self.static_func())
+
+ prints("test_self before:", self.static_var)
+ self.static_var = 2
+ prints("test_self after:", self.static_var)
+
+func test():
+ var hard := B.new()
+ hard.test_self()
+
+ print(hard.InnerClass is GDScript)
+ print(hard.NamedEnum)
+ print(hard.NamedEnum.VALUE)
+ print(hard.UNNAMED_ENUM_VALUE)
+ print(hard.CONSTANT)
+ @warning_ignore("static_called_on_instance")
+ print(hard.static_func())
+
+ prints("hard before:", hard.static_var)
+ hard.static_var = 3
+ prints("hard after:", hard.static_var)
+
+ var weak: Variant = B.new()
+ print(weak.InnerClass is GDScript)
+ print(weak.NamedEnum)
+ print(weak.NamedEnum.VALUE)
+ print(weak.UNNAMED_ENUM_VALUE)
+ print(weak.CONSTANT)
+ @warning_ignore("unsafe_method_access")
+ print(weak.static_func())
+
+ prints("weak before:", weak.static_var)
+ weak.static_var = 4
+ prints("weak after:", weak.static_var)
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.out b/modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.out
new file mode 100644
index 0000000000..7d7ad04df4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.out
@@ -0,0 +1,25 @@
+GDTEST_OK
+true
+{ "VALUE": 111 }
+111
+222
+333
+444
+test_self before: 1
+test_self after: 2
+true
+{ "VALUE": 111 }
+111
+222
+333
+444
+hard before: 2
+hard after: 3
+true
+{ "VALUE": 111 }
+111
+222
+333
+444
+weak before: 3
+weak after: 4
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.gd
new file mode 100644
index 0000000000..65635daa36
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.gd
@@ -0,0 +1,17 @@
+# GH-41919
+
+class_name TestStaticFuncAsCallable
+
+class InnerClass:
+ static func inner_my_func():
+ print("inner_my_func")
+
+static func my_func():
+ print("my_func")
+
+var a: Callable = TestStaticFuncAsCallable.my_func
+var b: Callable = InnerClass.inner_my_func
+
+func test():
+ a.call()
+ b.call()
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.out
new file mode 100644
index 0000000000..360bb9f322
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+my_func
+inner_my_func
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd
index e193312381..8da8bb7e53 100644
--- a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd
@@ -33,24 +33,24 @@ func test():
prints("perm:", perm)
prints("prop:", prop)
- print("other.perm:", StaticVariablesOther.perm)
- print("other.prop:", StaticVariablesOther.prop)
+ prints("other.perm:", StaticVariablesOther.perm)
+ prints("other.prop:", StaticVariablesOther.prop)
StaticVariablesOther.perm = 2
StaticVariablesOther.prop = "foo"
- print("other.perm:", StaticVariablesOther.perm)
- print("other.prop:", StaticVariablesOther.prop)
+ prints("other.perm:", StaticVariablesOther.perm)
+ prints("other.prop:", StaticVariablesOther.prop)
@warning_ignore("unsafe_method_access")
var path = get_script().get_path().get_base_dir()
- var other = load(path + "/static_variables_load.gd")
+ var other = load(path + "/static_variables_load.gd")
- print("load.perm:", other.perm)
- print("load.prop:", other.prop)
+ prints("load.perm:", other.perm)
+ prints("load.prop:", other.prop)
other.perm = 3
other.prop = "bar"
- print("load.perm:", other.perm)
- print("load.prop:", other.prop)
+ prints("load.perm:", other.perm)
+ prints("load.prop:", other.prop)
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables.out b/modules/gdscript/tests/scripts/runtime/features/static_variables.out
index d2491aef5e..650e1d9578 100644
--- a/modules/gdscript/tests/scripts/runtime/features/static_variables.out
+++ b/modules/gdscript/tests/scripts/runtime/features/static_variables.out
@@ -3,14 +3,14 @@ Inner._static_init inner
InnerInner._static_init inner inner
data: data
perm: 0
-prop: prefix Hello! suffix
+prop: Hello! suffix
perm: 1
prop: prefix World! suffix
-other.perm:0
-other.prop:prefix Hello! suffix
-other.perm:2
-other.prop:prefix foo suffix
-load.perm:0
-load.prop:prefix Hello! suffix
-load.perm:3
-load.prop:prefix bar suffix
+other.perm: 0
+other.prop: Hello! suffix
+other.perm: 2
+other.prop: prefix foo suffix
+load.perm: 0
+load.prop: Hello! suffix
+load.perm: 3
+load.prop: prefix bar suffix
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables_2.gd b/modules/gdscript/tests/scripts/runtime/features/static_variables_2.gd
new file mode 100644
index 0000000000..7a75d119ed
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_variables_2.gd
@@ -0,0 +1,56 @@
+@static_unload
+
+class A:
+ static var x: int = 1
+
+ static var y: int = 42:
+ set(_value):
+ print("The setter is NOT called on initialization.") # GH-77098 p.1
+
+ static func _static_init() -> void:
+ prints("A _static_init begin:", x)
+ x = -1
+ prints("A _static_init end:", x)
+
+ static func sf(p_x: int) -> void:
+ x = p_x
+ prints("sf:", x)
+
+ # GH-77331
+ func f(p_x: int) -> void:
+ x = p_x
+ prints("f:", x)
+
+class B extends A:
+ static func _static_init() -> void:
+ prints("B _static_init begin:", x)
+ x = -2
+ prints("B _static_init end:", x)
+
+ static func sg(p_x: int) -> void:
+ x = p_x
+ prints("sg:", x)
+
+ func g(p_x: int) -> void:
+ x = p_x
+ prints("g:", x)
+
+ func h(p_x: int) -> void:
+ print("h: call f(%d)" % p_x)
+ f(p_x)
+
+func test():
+ prints(A.x, B.x)
+ A.x = 1 # GH-77098 p.2
+ prints(A.x, B.x)
+ B.x = 2
+ prints(A.x, B.x)
+
+ A.sf(3)
+ B.sf(4)
+ B.sg(5)
+
+ var b := B.new()
+ b.f(6)
+ b.g(7)
+ b.h(8)
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables_2.out b/modules/gdscript/tests/scripts/runtime/features/static_variables_2.out
new file mode 100644
index 0000000000..b833911d95
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/static_variables_2.out
@@ -0,0 +1,15 @@
+GDTEST_OK
+A _static_init begin: 1
+A _static_init end: -1
+B _static_init begin: -1
+B _static_init end: -2
+-2 -2
+1 1
+2 2
+sf: 3
+sf: 4
+sg: 5
+f: 6
+g: 7
+h: call f(8)
+f: 8