Преглед на файлове

GDScript: Fix some bugs with static variables and functions

Danil Alexeev преди 2 години
родител
ревизия
aebbbda080
променени са 21 файла, в които са добавени 614 реда и са изтрити 184 реда
  1. 177 102
      modules/gdscript/gdscript.cpp
  2. 1 0
      modules/gdscript/gdscript.h
  3. 8 2
      modules/gdscript/gdscript_analyzer.cpp
  4. 14 0
      modules/gdscript/gdscript_byte_codegen.cpp
  5. 2 2
      modules/gdscript/gdscript_byte_codegen.h
  6. 2 1
      modules/gdscript/gdscript_codegen.h
  7. 121 51
      modules/gdscript/gdscript_compiler.cpp
  8. 30 0
      modules/gdscript/gdscript_disassembler.cpp
  9. 5 2
      modules/gdscript/gdscript_function.h
  10. 9 1
      modules/gdscript/gdscript_parser.cpp
  11. 43 5
      modules/gdscript/gdscript_vm.cpp
  12. 8 0
      modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.gd
  13. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.out
  14. 58 0
      modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.gd
  15. 25 0
      modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.out
  16. 17 0
      modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.gd
  17. 3 0
      modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.out
  18. 9 9
      modules/gdscript/tests/scripts/runtime/features/static_variables.gd
  19. 9 9
      modules/gdscript/tests/scripts/runtime/features/static_variables.out
  20. 56 0
      modules/gdscript/tests/scripts/runtime/features/static_variables_2.gd
  21. 15 0
      modules/gdscript/tests/scripts/runtime/features/static_variables_2.out

+ 177 - 102
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;
 			}
 		}
 

+ 1 - 0
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;

+ 8 - 2
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;
 					}

+ 14 - 0
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: {

+ 2 - 2
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;

+ 2 - 1
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;

+ 121 - 51
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();
 		}
 	}

+ 30 - 0
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);

+ 5 - 2
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),

+ 9 - 1
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;

+ 43 - 5
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();
 	}
 

+ 8 - 0
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

+ 2 - 0
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.

+ 58 - 0
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)

+ 25 - 0
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

+ 17 - 0
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()

+ 3 - 0
modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.out

@@ -0,0 +1,3 @@
+GDTEST_OK
+my_func
+inner_my_func

+ 9 - 9
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)

+ 9 - 9
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

+ 56 - 0
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)

+ 15 - 0
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