Browse Source

Add support for static variables in GDScript

Which allows editable data associated with a particular class instead of
the instance. Scripts with static variables are kept in memory
indefinitely unless the `@static_unload` annotation is used or the
`static_unload()` method is called on the GDScript.

If the custom function `_static_init()` exists it will be called when
the class is loaded, after the static variables are set.
George Marques 2 years ago
parent
commit
0ba6048ad3
36 changed files with 689 additions and 86 deletions
  1. 3 0
      doc/classes/ProjectSettings.xml
  2. 6 0
      modules/gdscript/doc_classes/@GDScript.xml
  3. 121 6
      modules/gdscript/gdscript.cpp
  4. 14 0
      modules/gdscript/gdscript.h
  5. 69 16
      modules/gdscript/gdscript_analyzer.cpp
  6. 1 0
      modules/gdscript/gdscript_analyzer.h
  7. 2 0
      modules/gdscript/gdscript_byte_codegen.h
  8. 10 0
      modules/gdscript/gdscript_cache.cpp
  9. 3 0
      modules/gdscript/gdscript_cache.h
  10. 1 0
      modules/gdscript/gdscript_codegen.h
  11. 211 17
      modules/gdscript/gdscript_compiler.cpp
  12. 3 0
      modules/gdscript/gdscript_compiler.h
  13. 9 0
      modules/gdscript/gdscript_editor.cpp
  14. 2 1
      modules/gdscript/gdscript_function.h
  15. 58 28
      modules/gdscript/gdscript_parser.cpp
  16. 13 8
      modules/gdscript/gdscript_parser.h
  17. 2 2
      modules/gdscript/gdscript_vm.cpp
  18. 4 0
      modules/gdscript/gdscript_warning.cpp
  19. 2 0
      modules/gdscript/gdscript_warning.h
  20. 8 8
      modules/gdscript/tests/gdscript_test_runner.cpp
  21. 5 0
      modules/gdscript/tests/scripts/analyzer/errors/static_constructor_with_return_type.gd
  22. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/static_constructor_with_return_type.out
  23. 9 0
      modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.gd
  24. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out
  25. 5 0
      modules/gdscript/tests/scripts/parser/errors/static_constructor_not_static.gd
  26. 2 0
      modules/gdscript/tests/scripts/parser/errors/static_constructor_not_static.out
  27. 6 0
      modules/gdscript/tests/scripts/parser/errors/static_constructor_returning_something.gd
  28. 2 0
      modules/gdscript/tests/scripts/parser/errors/static_constructor_returning_something.out
  29. 13 0
      modules/gdscript/tests/scripts/runtime/features/static_constructor.gd
  30. 4 0
      modules/gdscript/tests/scripts/runtime/features/static_constructor.out
  31. 56 0
      modules/gdscript/tests/scripts/runtime/features/static_variables.gd
  32. 16 0
      modules/gdscript/tests/scripts/runtime/features/static_variables.out
  33. 10 0
      modules/gdscript/tests/scripts/runtime/features/static_variables_load.gd
  34. 2 0
      modules/gdscript/tests/scripts/runtime/features/static_variables_load.out
  35. 11 0
      modules/gdscript/tests/scripts/runtime/features/static_variables_other.gd
  36. 2 0
      modules/gdscript/tests/scripts/runtime/features/static_variables_other.out

+ 3 - 0
doc/classes/ProjectSettings.xml

@@ -456,6 +456,9 @@
 		<member name="debug/gdscript/warnings/redundant_await" type="int" setter="" getter="" default="1">
 		<member name="debug/gdscript/warnings/redundant_await" type="int" setter="" getter="" default="1">
 			When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a function that is not a coroutine is called with await.
 			When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a function that is not a coroutine is called with await.
 		</member>
 		</member>
+		<member name="debug/gdscript/warnings/redundant_static_unload" type="int" setter="" getter="" default="1">
+			When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when the [code]@static_unload[/code] annotation is used in a script without any static variables.
+		</member>
 		<member name="debug/gdscript/warnings/renamed_in_godot_4_hint" type="bool" setter="" getter="" default="1">
 		<member name="debug/gdscript/warnings/renamed_in_godot_4_hint" type="bool" setter="" getter="" default="1">
 			When enabled, using a property, enum, or function that was renamed since Godot 3 will produce a hint if an error occurs.
 			When enabled, using a property, enum, or function that was renamed since Godot 3 will produce a hint if an error occurs.
 		</member>
 		</member>

+ 6 - 0
modules/gdscript/doc_classes/@GDScript.xml

@@ -622,6 +622,12 @@
 				[/codeblock]
 				[/codeblock]
 			</description>
 			</description>
 		</annotation>
 		</annotation>
+		<annotation name="@static_unload">
+			<return type="void" />
+			<description>
+				Make a script with static variables to not persist after all references are lost. If the script is loaded again the static variables will revert to their default values.
+			</description>
+		</annotation>
 		<annotation name="@tool">
 		<annotation name="@tool">
 			<return type="void" />
 			<return type="void" />
 			<description>
 			<description>

+ 121 - 6
modules/gdscript/gdscript.cpp

@@ -651,6 +651,49 @@ String GDScript::_get_debug_path() const {
 	}
 	}
 }
 }
 
 
+Error GDScript::_static_init() {
+	if (static_initializer) {
+		Callable::CallError call_err;
+		static_initializer->call(nullptr, nullptr, 0, call_err);
+		if (call_err.error != Callable::CallError::CALL_OK) {
+			return ERR_CANT_CREATE;
+		}
+	}
+	Error err = OK;
+	for (KeyValue<StringName, Ref<GDScript>> &inner : subclasses) {
+		err = inner.value->_static_init();
+		if (err) {
+			break;
+		}
+	}
+	return err;
+}
+
+#ifdef TOOLS_ENABLED
+
+void GDScript::_save_old_static_data() {
+	old_static_variables_indices = static_variables_indices;
+	old_static_variables = static_variables;
+	for (KeyValue<StringName, Ref<GDScript>> &inner : subclasses) {
+		inner.value->_save_old_static_data();
+	}
+}
+
+void GDScript::_restore_old_static_data() {
+	for (KeyValue<StringName, MemberInfo> &E : old_static_variables_indices) {
+		if (static_variables_indices.has(E.key)) {
+			static_variables.write[static_variables_indices[E.key].index] = old_static_variables[E.value.index];
+		}
+	}
+	old_static_variables_indices.clear();
+	old_static_variables.clear();
+	for (KeyValue<StringName, Ref<GDScript>> &inner : subclasses) {
+		inner.value->_restore_old_static_data();
+	}
+}
+
+#endif
+
 Error GDScript::reload(bool p_keep_state) {
 Error GDScript::reload(bool p_keep_state) {
 	if (reloading) {
 	if (reloading) {
 		return OK;
 		return OK;
@@ -696,6 +739,14 @@ Error GDScript::reload(bool p_keep_state) {
 		}
 		}
 	}
 	}
 
 
+	bool can_run = ScriptServer::is_scripting_enabled() || is_tool();
+
+#ifdef DEBUG_ENABLED
+	if (p_keep_state && can_run && is_valid()) {
+		_save_old_static_data();
+	}
+#endif
+
 	valid = false;
 	valid = false;
 	GDScriptParser parser;
 	GDScriptParser parser;
 	Error err = parser.parse(source, path, false);
 	Error err = parser.parse(source, path, false);
@@ -726,7 +777,7 @@ Error GDScript::reload(bool p_keep_state) {
 		return ERR_PARSE_ERROR;
 		return ERR_PARSE_ERROR;
 	}
 	}
 
 
-	bool can_run = ScriptServer::is_scripting_enabled() || parser.is_tool();
+	can_run = ScriptServer::is_scripting_enabled() || parser.is_tool();
 
 
 	GDScriptCompiler compiler;
 	GDScriptCompiler compiler;
 	err = compiler.compile(&parser, this, p_keep_state);
 	err = compiler.compile(&parser, this, p_keep_state);
@@ -760,6 +811,19 @@ Error GDScript::reload(bool p_keep_state) {
 	}
 	}
 #endif
 #endif
 
 
+	if (can_run) {
+		err = _static_init();
+		if (err) {
+			return err;
+		}
+	}
+
+#ifdef DEBUG_ENABLED
+	if (can_run && p_keep_state) {
+		_restore_old_static_data();
+	}
+#endif
+
 	reloading = false;
 	reloading = false;
 	return OK;
 	return OK;
 }
 }
@@ -788,6 +852,10 @@ const Variant GDScript::get_rpc_config() const {
 	return rpc_config;
 	return rpc_config;
 }
 }
 
 
+void GDScript::unload_static() const {
+	GDScriptCache::remove_script(fully_qualified_name);
+}
+
 Variant GDScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
 Variant GDScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
 	GDScript *top = this;
 	GDScript *top = this;
 	while (top) {
 	while (top) {
@@ -824,6 +892,19 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const {
 					return true;
 					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;
+				}
+			}
 			top = top->_base;
 			top = top->_base;
 		}
 		}
 
 
@@ -841,7 +922,32 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) {
 		set_source_code(p_value);
 		set_source_code(p_value);
 		reload();
 		reload();
 	} else {
 	} else {
-		return false;
+		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;
+				}
+			}
+			top = top->_base;
+		}
 	}
 	}
 
 
 	return true;
 	return true;
@@ -1293,6 +1399,13 @@ void GDScript::clear(GDScript::ClearData *p_clear_data) {
 		E.value.data_type.script_type_ref = Ref<Script>();
 		E.value.data_type.script_type_ref = Ref<Script>();
 	}
 	}
 
 
+	for (KeyValue<StringName, GDScript::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>();
+	}
+	static_variables.clear();
+	static_variables_indices.clear();
+
 	if (implicit_initializer) {
 	if (implicit_initializer) {
 		clear_data->functions.insert(implicit_initializer);
 		clear_data->functions.insert(implicit_initializer);
 		implicit_initializer = nullptr;
 		implicit_initializer = nullptr;
@@ -1303,6 +1416,11 @@ void GDScript::clear(GDScript::ClearData *p_clear_data) {
 		implicit_ready = nullptr;
 		implicit_ready = nullptr;
 	}
 	}
 
 
+	if (static_initializer) {
+		clear_data->functions.insert(static_initializer);
+		static_initializer = nullptr;
+	}
+
 	_save_orphaned_subclasses(clear_data);
 	_save_orphaned_subclasses(clear_data);
 
 
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
@@ -1357,10 +1475,6 @@ GDScript::~GDScript() {
 
 
 		GDScriptLanguage::get_singleton()->script_list.remove(&script_list);
 		GDScriptLanguage::get_singleton()->script_list.remove(&script_list);
 	}
 	}
-
-	if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown.
-		GDScriptCache::remove_script(get_path());
-	}
 }
 }
 
 
 //////////////////////////////
 //////////////////////////////
@@ -2391,6 +2505,7 @@ GDScriptLanguage::GDScriptLanguage() {
 	ERR_FAIL_COND(singleton);
 	ERR_FAIL_COND(singleton);
 	singleton = this;
 	singleton = this;
 	strings._init = StaticCString::create("_init");
 	strings._init = StaticCString::create("_init");
+	strings._static_init = StaticCString::create("_static_init");
 	strings._notification = StaticCString::create("_notification");
 	strings._notification = StaticCString::create("_notification");
 	strings._set = StaticCString::create("_set");
 	strings._set = StaticCString::create("_set");
 	strings._get = StaticCString::create("_get");
 	strings._get = StaticCString::create("_get");

+ 14 - 0
modules/gdscript/gdscript.h

@@ -94,6 +94,8 @@ class GDScript : public Script {
 
 
 	HashSet<StringName> members; //members are just indices to the instantiated script.
 	HashSet<StringName> members; //members are just indices to the instantiated script.
 	HashMap<StringName, Variant> constants;
 	HashMap<StringName, Variant> constants;
+	HashMap<StringName, MemberInfo> static_variables_indices;
+	Vector<Variant> static_variables;
 	HashMap<StringName, GDScriptFunction *> member_functions;
 	HashMap<StringName, GDScriptFunction *> member_functions;
 	HashMap<StringName, MemberInfo> member_indices; //members are just indices to the instantiated script.
 	HashMap<StringName, MemberInfo> member_indices; //members are just indices to the instantiated script.
 	HashMap<StringName, Ref<GDScript>> subclasses;
 	HashMap<StringName, Ref<GDScript>> subclasses;
@@ -102,6 +104,12 @@ class GDScript : public Script {
 
 
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 
 
+	// For static data storage during hot-reloading.
+	HashMap<StringName, MemberInfo> old_static_variables_indices;
+	Vector<Variant> old_static_variables;
+	void _save_old_static_data();
+	void _restore_old_static_data();
+
 	HashMap<StringName, int> member_lines;
 	HashMap<StringName, int> member_lines;
 	HashMap<StringName, Variant> member_default_values;
 	HashMap<StringName, Variant> member_default_values;
 	List<PropertyInfo> members_cache;
 	List<PropertyInfo> members_cache;
@@ -123,6 +131,9 @@ class GDScript : public Script {
 	GDScriptFunction *implicit_initializer = nullptr;
 	GDScriptFunction *implicit_initializer = nullptr;
 	GDScriptFunction *initializer = nullptr; //direct pointer to new , faster to locate
 	GDScriptFunction *initializer = nullptr; //direct pointer to new , faster to locate
 	GDScriptFunction *implicit_ready = nullptr;
 	GDScriptFunction *implicit_ready = nullptr;
+	GDScriptFunction *static_initializer = nullptr;
+
+	Error _static_init();
 
 
 	int subclass_count = 0;
 	int subclass_count = 0;
 	RBSet<Object *> instances;
 	RBSet<Object *> instances;
@@ -268,6 +279,8 @@ public:
 
 
 	virtual const Variant get_rpc_config() const override;
 	virtual const Variant get_rpc_config() const override;
 
 
+	void unload_static() const;
+
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 	virtual bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; }
 	virtual bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; }
 #endif
 #endif
@@ -439,6 +452,7 @@ public:
 
 
 	struct {
 	struct {
 		StringName _init;
 		StringName _init;
+		StringName _static_init;
 		StringName _notification;
 		StringName _notification;
 		StringName _set;
 		StringName _set;
 		StringName _get;
 		StringName _get;

+ 69 - 16
modules/gdscript/gdscript_analyzer.cpp

@@ -879,6 +879,8 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
 #endif
 #endif
 		switch (member.type) {
 		switch (member.type) {
 			case GDScriptParser::ClassNode::Member::VARIABLE: {
 			case GDScriptParser::ClassNode::Member::VARIABLE: {
+				bool previous_static_context = static_context;
+				static_context = member.variable->is_static;
 				check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable);
 				check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable);
 				member.variable->set_datatype(resolving_datatype);
 				member.variable->set_datatype(resolving_datatype);
 				resolve_variable(member.variable, false);
 				resolve_variable(member.variable, false);
@@ -890,6 +892,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
 						E->apply(parser, member.variable);
 						E->apply(parser, member.variable);
 					}
 					}
 				}
 				}
+				static_context = previous_static_context;
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
 				if (member.variable->exported && member.variable->onready) {
 				if (member.variable->exported && member.variable->onready) {
 					parser->push_warning(member.variable, GDScriptWarning::ONREADY_WITH_EXPORT);
 					parser->push_warning(member.variable, GDScriptWarning::ONREADY_WITH_EXPORT);
@@ -897,7 +900,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
 				if (member.variable->initializer) {
 				if (member.variable->initializer) {
 					// Check if it is call to get_node() on self (using shorthand $ or not), so we can check if @onready is needed.
 					// Check if it is call to get_node() on self (using shorthand $ or not), so we can check if @onready is needed.
 					// This could be improved by traversing the expression fully and checking the presence of get_node at any level.
 					// This could be improved by traversing the expression fully and checking the presence of get_node at any level.
-					if (!member.variable->onready && member.variable->initializer && (member.variable->initializer->type == GDScriptParser::Node::GET_NODE || member.variable->initializer->type == GDScriptParser::Node::CALL || member.variable->initializer->type == GDScriptParser::Node::CAST)) {
+					if (!member.variable->is_static && !member.variable->onready && member.variable->initializer && (member.variable->initializer->type == GDScriptParser::Node::GET_NODE || member.variable->initializer->type == GDScriptParser::Node::CALL || member.variable->initializer->type == GDScriptParser::Node::CAST)) {
 						GDScriptParser::Node *expr = member.variable->initializer;
 						GDScriptParser::Node *expr = member.variable->initializer;
 						if (expr->type == GDScriptParser::Node::CAST) {
 						if (expr->type == GDScriptParser::Node::CAST) {
 							expr = static_cast<GDScriptParser::CastNode *>(expr)->operand;
 							expr = static_cast<GDScriptParser::CastNode *>(expr)->operand;
@@ -1082,6 +1085,10 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
 		p_source = p_class;
 		p_source = p_class;
 	}
 	}
 
 
+#ifdef DEBUG_ENABLED
+	bool has_static_data = p_class->has_static_data;
+#endif
+
 	if (!p_class->resolved_interface) {
 	if (!p_class->resolved_interface) {
 		if (!parser->has_class(p_class)) {
 		if (!parser->has_class(p_class)) {
 			String script_path = p_class->get_datatype().script_path;
 			String script_path = p_class->get_datatype().script_path;
@@ -1124,7 +1131,29 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
 
 
 		for (int i = 0; i < p_class->members.size(); i++) {
 		for (int i = 0; i < p_class->members.size(); i++) {
 			resolve_class_member(p_class, i);
 			resolve_class_member(p_class, i);
+
+#ifdef DEBUG_ENABLED
+			if (!has_static_data) {
+				GDScriptParser::ClassNode::Member member = p_class->members[i];
+				if (member.type == GDScriptParser::ClassNode::Member::CLASS) {
+					has_static_data = member.m_class->has_static_data;
+				}
+			}
+#endif
+		}
+
+#ifdef DEBUG_ENABLED
+		if (!has_static_data && p_class->annotated_static_unload) {
+			GDScriptParser::Node *static_unload = nullptr;
+			for (GDScriptParser::AnnotationNode *node : p_class->annotations) {
+				if (node->name == "@static_unload") {
+					static_unload = node;
+					break;
+				}
+			}
+			parser->push_warning(static_unload ? static_unload : p_class, GDScriptWarning::REDUNDANT_STATIC_UNLOAD);
 		}
 		}
+#endif
 	}
 	}
 }
 }
 
 
@@ -1499,6 +1528,8 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
 
 
 	GDScriptParser::FunctionNode *previous_function = parser->current_function;
 	GDScriptParser::FunctionNode *previous_function = parser->current_function;
 	parser->current_function = p_function;
 	parser->current_function = p_function;
+	bool previous_static_context = static_context;
+	static_context = p_function->is_static;
 
 
 	GDScriptParser::DataType prev_datatype = p_function->get_datatype();
 	GDScriptParser::DataType prev_datatype = p_function->get_datatype();
 
 
@@ -1542,6 +1573,18 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
 				push_error("Constructor cannot have an explicit return type.", p_function->return_type);
 				push_error("Constructor cannot have an explicit return type.", p_function->return_type);
 			}
 			}
 		}
 		}
+	} else if (!p_is_lambda && function_name == GDScriptLanguage::get_singleton()->strings._static_init) {
+		// Static constructor.
+		GDScriptParser::DataType return_type;
+		return_type.kind = GDScriptParser::DataType::BUILTIN;
+		return_type.builtin_type = Variant::NIL;
+		p_function->set_datatype(return_type);
+		if (p_function->return_type) {
+			GDScriptParser::DataType declared_return = resolve_datatype(p_function->return_type);
+			if (declared_return.kind != GDScriptParser::DataType::BUILTIN || declared_return.builtin_type != Variant::NIL) {
+				push_error("Static constructor cannot have an explicit return type.", p_function->return_type);
+			}
+		}
 	} else {
 	} else {
 		if (p_function->return_type != nullptr) {
 		if (p_function->return_type != nullptr) {
 			p_function->set_datatype(type_from_metatype(resolve_datatype(p_function->return_type)));
 			p_function->set_datatype(type_from_metatype(resolve_datatype(p_function->return_type)));
@@ -1625,6 +1668,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
 	parser->ignored_warnings = previously_ignored_warnings;
 	parser->ignored_warnings = previously_ignored_warnings;
 #endif
 #endif
 	parser->current_function = previous_function;
 	parser->current_function = previous_function;
+	static_context = previous_static_context;
 }
 }
 
 
 void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_function, bool p_is_lambda) {
 void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_function, bool p_is_lambda) {
@@ -3050,13 +3094,17 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
 			base_type.is_meta_type = false;
 			base_type.is_meta_type = false;
 		}
 		}
 
 
-		if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) {
-			// Get the parent function above any lambda.
-			GDScriptParser::FunctionNode *parent_function = parser->current_function;
-			while (parent_function->source_lambda) {
-				parent_function = parent_function->source_lambda->parent_function;
+		if (is_self && static_context && !is_static) {
+			if (parser->current_function) {
+				// Get the parent function above any lambda.
+				GDScriptParser::FunctionNode *parent_function = parser->current_function;
+				while (parent_function->source_lambda) {
+					parent_function = parent_function->source_lambda->parent_function;
+				}
+				push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parent_function->identifier->name), p_call);
+			} else {
+				push_error(vformat(R"*(Cannot call non-static function "%s()" for static variable initializer.)*", p_call->function_name), p_call);
 			}
 			}
-			push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parent_function->identifier->name), p_call);
 		} else if (!is_self && base_type.is_meta_type && !is_static) {
 		} else if (!is_self && base_type.is_meta_type && !is_static) {
 			base_type.is_meta_type = false; // For `to_string()`.
 			base_type.is_meta_type = false; // For `to_string()`.
 			push_error(vformat(R"*(Cannot call non-static function "%s()" on the class "%s" directly. Make an instance instead.)*", p_call->function_name, base_type.to_string()), p_call);
 			push_error(vformat(R"*(Cannot call non-static function "%s()" on the class "%s" directly. Make an instance instead.)*", p_call->function_name, base_type.to_string()), p_call);
@@ -3073,7 +3121,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
 			parser->push_warning(p_call, GDScriptWarning::RETURN_VALUE_DISCARDED, p_call->function_name);
 			parser->push_warning(p_call, GDScriptWarning::RETURN_VALUE_DISCARDED, p_call->function_name);
 		}
 		}
 
 
-		if (is_static && !base_type.is_meta_type && !(is_self && parser->current_function != nullptr && parser->current_function->is_static)) {
+		if (is_static && !is_constructor && !base_type.is_meta_type && !(is_self && static_context)) {
 			String caller_type = String(base_type.native_type);
 			String caller_type = String(base_type.native_type);
 
 
 			if (caller_type.is_empty()) {
 			if (caller_type.is_empty()) {
@@ -3428,9 +3476,9 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
 				}
 				}
 
 
 				case GDScriptParser::ClassNode::Member::VARIABLE: {
 				case GDScriptParser::ClassNode::Member::VARIABLE: {
-					if (is_base && !base.is_meta_type) {
+					if (is_base && (!base.is_meta_type || member.variable->is_static)) {
 						p_identifier->set_datatype(member.get_datatype());
 						p_identifier->set_datatype(member.get_datatype());
-						p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE;
+						p_identifier->source = member.variable->is_static ? GDScriptParser::IdentifierNode::STATIC_VARIABLE : GDScriptParser::IdentifierNode::MEMBER_VARIABLE;
 						p_identifier->variable_source = member.variable;
 						p_identifier->variable_source = member.variable;
 						member.variable->usages += 1;
 						member.variable->usages += 1;
 						return;
 						return;
@@ -3572,6 +3620,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
 			mark_lambda_use_self();
 			mark_lambda_use_self();
 			p_identifier->variable_source->usages++;
 			p_identifier->variable_source->usages++;
 			[[fallthrough]];
 			[[fallthrough]];
+		case GDScriptParser::IdentifierNode::STATIC_VARIABLE:
 		case GDScriptParser::IdentifierNode::LOCAL_VARIABLE:
 		case GDScriptParser::IdentifierNode::LOCAL_VARIABLE:
 			p_identifier->set_datatype(p_identifier->variable_source->get_datatype());
 			p_identifier->set_datatype(p_identifier->variable_source->get_datatype());
 			found_source = true;
 			found_source = true;
@@ -3602,13 +3651,17 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
 	if (found_source) {
 	if (found_source) {
 		bool source_is_variable = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE;
 		bool source_is_variable = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE;
 		bool source_is_signal = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_SIGNAL;
 		bool source_is_signal = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_SIGNAL;
-		if ((source_is_variable || source_is_signal) && parser->current_function && parser->current_function->is_static) {
-			// Get the parent function above any lambda.
-			GDScriptParser::FunctionNode *parent_function = parser->current_function;
-			while (parent_function->source_lambda) {
-				parent_function = parent_function->source_lambda->parent_function;
+		if ((source_is_variable || source_is_signal) && static_context) {
+			if (parser->current_function) {
+				// Get the parent function above any lambda.
+				GDScriptParser::FunctionNode *parent_function = parser->current_function;
+				while (parent_function->source_lambda) {
+					parent_function = parent_function->source_lambda->parent_function;
+				}
+				push_error(vformat(R"*(Cannot access %s "%s" from the static function "%s()".)*", source_is_signal ? "signal" : "instance variable", p_identifier->name, parent_function->identifier->name), p_identifier);
+			} else {
+				push_error(vformat(R"*(Cannot access %s "%s" for a static variable initializer.)*", source_is_signal ? "signal" : "instance variable", p_identifier->name), p_identifier);
 			}
 			}
-			push_error(vformat(R"*(Cannot access %s "%s" from the static function "%s()".)*", source_is_signal ? "signal" : "instance variable", p_identifier->name, parent_function->identifier->name), p_identifier);
 		}
 		}
 
 
 		if (!lambda_stack.is_empty()) {
 		if (!lambda_stack.is_empty()) {

+ 1 - 0
modules/gdscript/gdscript_analyzer.h

@@ -43,6 +43,7 @@ class GDScriptAnalyzer {
 
 
 	const GDScriptParser::EnumNode *current_enum = nullptr;
 	const GDScriptParser::EnumNode *current_enum = nullptr;
 	List<GDScriptParser::LambdaNode *> lambda_stack;
 	List<GDScriptParser::LambdaNode *> lambda_stack;
+	bool static_context = false;
 
 
 	// Tests for detecting invalid overloading of script members
 	// Tests for detecting invalid overloading of script members
 	static _FORCE_INLINE_ bool has_member_name_conflict_in_script_class(const StringName &p_name, const GDScriptParser::ClassNode *p_current_class_node, const GDScriptParser::Node *p_member);
 	static _FORCE_INLINE_ bool has_member_name_conflict_in_script_class(const StringName &p_name, const GDScriptParser::ClassNode *p_current_class_node, const GDScriptParser::Node *p_member);

+ 2 - 0
modules/gdscript/gdscript_byte_codegen.h

@@ -366,6 +366,8 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
 				return p_address.address | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS);
 				return p_address.address | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS);
 			case Address::CONSTANT:
 			case Address::CONSTANT:
 				return p_address.address | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS);
 				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::LOCAL_VARIABLE:
 			case Address::FUNCTION_PARAMETER:
 			case Address::FUNCTION_PARAMETER:
 				return p_address.address | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
 				return p_address.address | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);

+ 10 - 0
modules/gdscript/gdscript_cache.cpp

@@ -342,6 +342,16 @@ Error GDScriptCache::finish_compiling(const String &p_owner) {
 	return err;
 	return err;
 }
 }
 
 
+void GDScriptCache::add_static_script(Ref<GDScript> p_script) {
+	ERR_FAIL_COND_MSG(p_script.is_null(), "Trying to cache empty script as static.");
+	ERR_FAIL_COND_MSG(!p_script->is_valid(), "Trying to cache non-compiled script as static.");
+	singleton->static_gdscript_cache[p_script->get_fully_qualified_name()] = p_script;
+}
+
+void GDScriptCache::remove_static_script(const String &p_fqcn) {
+	singleton->static_gdscript_cache.erase(p_fqcn);
+}
+
 Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_error, const String &p_owner) {
 Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_error, const String &p_owner) {
 	MutexLock lock(singleton->mutex);
 	MutexLock lock(singleton->mutex);
 
 

+ 3 - 0
modules/gdscript/gdscript_cache.h

@@ -78,6 +78,7 @@ class GDScriptCache {
 	HashMap<String, GDScriptParserRef *> parser_map;
 	HashMap<String, GDScriptParserRef *> parser_map;
 	HashMap<String, Ref<GDScript>> shallow_gdscript_cache;
 	HashMap<String, Ref<GDScript>> shallow_gdscript_cache;
 	HashMap<String, Ref<GDScript>> full_gdscript_cache;
 	HashMap<String, Ref<GDScript>> full_gdscript_cache;
+	HashMap<String, Ref<GDScript>> static_gdscript_cache;
 	HashMap<String, HashSet<String>> dependencies;
 	HashMap<String, HashSet<String>> dependencies;
 	HashMap<String, Ref<PackedScene>> packed_scene_cache;
 	HashMap<String, Ref<PackedScene>> packed_scene_cache;
 	HashMap<String, HashSet<String>> packed_scene_dependencies;
 	HashMap<String, HashSet<String>> packed_scene_dependencies;
@@ -101,6 +102,8 @@ public:
 	static Ref<GDScript> get_full_script(const String &p_path, Error &r_error, const String &p_owner = String(), bool p_update_from_disk = false);
 	static Ref<GDScript> get_full_script(const String &p_path, Error &r_error, const String &p_owner = String(), bool p_update_from_disk = false);
 	static Ref<GDScript> get_cached_script(const String &p_path);
 	static Ref<GDScript> get_cached_script(const String &p_path);
 	static Error finish_compiling(const String &p_owner);
 	static Error finish_compiling(const String &p_owner);
+	static void add_static_script(Ref<GDScript> p_script);
+	static void remove_static_script(const String &p_fqcn);
 
 
 	static Ref<PackedScene> get_packed_scene(const String &p_path, Error &r_error, const String &p_owner = "");
 	static Ref<PackedScene> get_packed_scene(const String &p_path, Error &r_error, const String &p_owner = "");
 	static void clear_unreferenced_packed_scenes();
 	static void clear_unreferenced_packed_scenes();

+ 1 - 0
modules/gdscript/gdscript_codegen.h

@@ -44,6 +44,7 @@ public:
 			CLASS,
 			CLASS,
 			MEMBER,
 			MEMBER,
 			CONSTANT,
 			CONSTANT,
+			STATIC_VARIABLE,
 			LOCAL_VARIABLE,
 			LOCAL_VARIABLE,
 			FUNCTION_PARAMETER,
 			FUNCTION_PARAMETER,
 			TEMPORARY,
 			TEMPORARY,

+ 211 - 17
modules/gdscript/gdscript_compiler.cpp

@@ -254,13 +254,29 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
 						gen->write_call_self(temp, codegen.script->member_indices[identifier].getter, args);
 						gen->write_call_self(temp, codegen.script->member_indices[identifier].getter, args);
 						return temp;
 						return temp;
 					} else {
 					} else {
-						// No getter or inside getter: direct member access.,
+						// No getter or inside getter: direct member access.
 						int idx = codegen.script->member_indices[identifier].index;
 						int idx = codegen.script->member_indices[identifier].index;
 						return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, idx, codegen.script->get_member_type(identifier));
 						return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, idx, codegen.script->get_member_type(identifier));
 					}
 					}
 				}
 				}
 			}
 			}
 
 
+			// 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);
+				}
+			}
+
 			// Try class constants.
 			// Try class constants.
 			{
 			{
 				GDScript *owner = codegen.script;
 				GDScript *owner = codegen.script;
@@ -563,7 +579,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
 								// Not exact arguments, but still can use method bind call.
 								// Not exact arguments, but still can use method bind call.
 								gen->write_call_method_bind(result, self, method, arguments);
 								gen->write_call_method_bind(result, self, method, arguments);
 							}
 							}
-						} else if ((codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") {
+						} else if (codegen.is_static || (codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") {
 							GDScriptCodeGenerator::Address self;
 							GDScriptCodeGenerator::Address self;
 							self.mode = GDScriptCodeGenerator::Address::CLASS;
 							self.mode = GDScriptCodeGenerator::Address::CLASS;
 							if (is_awaited) {
 							if (is_awaited) {
@@ -909,6 +925,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
 				bool is_member_property = false;
 				bool is_member_property = false;
 				bool member_property_has_setter = false;
 				bool member_property_has_setter = false;
 				bool member_property_is_in_setter = false;
 				bool member_property_is_in_setter = false;
+				bool is_static = false;
 				StringName member_property_setter_function;
 				StringName member_property_setter_function;
 
 
 				List<const GDScriptParser::SubscriptNode *> chain;
 				List<const GDScriptParser::SubscriptNode *> chain;
@@ -925,14 +942,16 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
 								StringName var_name = identifier->name;
 								StringName var_name = identifier->name;
 								if (_is_class_member_property(codegen, var_name)) {
 								if (_is_class_member_property(codegen, var_name)) {
 									assign_class_member_property = var_name;
 									assign_class_member_property = var_name;
-								} else if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) {
+								} else if (!_is_local_or_parameter(codegen, var_name) && (codegen.script->member_indices.has(var_name) || codegen.script->static_variables_indices.has(var_name))) {
 									is_member_property = true;
 									is_member_property = true;
-									member_property_setter_function = codegen.script->member_indices[var_name].setter;
+									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_has_setter = member_property_setter_function != StringName();
 									member_property_is_in_setter = member_property_has_setter && member_property_setter_function == codegen.function_name;
 									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 = codegen.script->member_indices[var_name].index;
-									target_member_property.type = codegen.script->member_indices[var_name].data_type;
+									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;
 								}
 								}
 							}
 							}
 							break;
 							break;
@@ -1085,7 +1104,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
 						if (member_property_has_setter && !member_property_is_in_setter) {
 						if (member_property_has_setter && !member_property_is_in_setter) {
 							Vector<GDScriptCodeGenerator::Address> args;
 							Vector<GDScriptCodeGenerator::Address> args;
 							args.push_back(assigned);
 							args.push_back(assigned);
-							gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), member_property_setter_function, args);
+							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);
 						} else {
 						} else {
 							gen->write_assign(target_member_property, assigned);
 							gen->write_assign(target_member_property, assigned);
 						}
 						}
@@ -1134,16 +1154,19 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
 				bool is_member = false;
 				bool is_member = false;
 				bool has_setter = false;
 				bool has_setter = false;
 				bool is_in_setter = false;
 				bool is_in_setter = false;
+				bool is_static = false;
 				StringName setter_function;
 				StringName setter_function;
 				StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name;
 				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)) {
+				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_member = true;
-					setter_function = codegen.script->member_indices[var_name].setter;
+					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();
 					has_setter = setter_function != StringName();
 					is_in_setter = has_setter && setter_function == codegen.function_name;
 					is_in_setter = has_setter && setter_function == codegen.function_name;
-					member.mode = GDScriptCodeGenerator::Address::MEMBER;
-					member.address = codegen.script->member_indices[var_name].index;
-					member.type = codegen.script->member_indices[var_name].data_type;
+					member.mode = is_static ? GDScriptCodeGenerator::Address::STATIC_VARIABLE : GDScriptCodeGenerator::Address::MEMBER;
+					member.address = minfo.index;
+					member.type = minfo.data_type;
 				}
 				}
 
 
 				GDScriptCodeGenerator::Address target;
 				GDScriptCodeGenerator::Address target;
@@ -2001,6 +2024,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
 	}
 	}
 
 
 	codegen.function_name = func_name;
 	codegen.function_name = func_name;
+	codegen.is_static = is_static;
 	codegen.generator->write_start(p_script, func_name, is_static, rpc_config, return_type);
 	codegen.generator->write_start(p_script, func_name, is_static, rpc_config, return_type);
 
 
 	int optional_parameters = 0;
 	int optional_parameters = 0;
@@ -2024,7 +2048,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
 	bool is_implicit_ready = !p_func && p_for_ready;
 	bool is_implicit_ready = !p_func && p_for_ready;
 
 
 	if (!p_for_lambda && is_implicit_initializer) {
 	if (!p_for_lambda && is_implicit_initializer) {
-		// Initialize the default values for type variables before anything.
+		// Initialize the default values for typed variables before anything.
 		// This avoids crashes if they are accessed with validated calls before being properly initialized.
 		// This avoids crashes if they are accessed with validated calls before being properly initialized.
 		// It may happen with out-of-order access or with `@onready` variables.
 		// It may happen with out-of-order access or with `@onready` variables.
 		for (const GDScriptParser::ClassNode::Member &member : p_class->members) {
 		for (const GDScriptParser::ClassNode::Member &member : p_class->members) {
@@ -2033,6 +2057,10 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
 			}
 			}
 
 
 			const GDScriptParser::VariableNode *field = member.variable;
 			const GDScriptParser::VariableNode *field = member.variable;
+			if (field->is_static) {
+				continue;
+			}
+
 			GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
 			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);
 			GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
 
 
@@ -2056,6 +2084,10 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
 				continue;
 				continue;
 			}
 			}
 			const GDScriptParser::VariableNode *field = p_class->members[i].variable;
 			const GDScriptParser::VariableNode *field = p_class->members[i].variable;
+			if (field->is_static) {
+				continue;
+			}
+
 			if (field->onready != is_implicit_ready) {
 			if (field->onready != is_implicit_ready) {
 				// Only initialize in @implicit_ready.
 				// Only initialize in @implicit_ready.
 				continue;
 				continue;
@@ -2183,6 +2215,135 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
 	return gd_function;
 	return gd_function;
 }
 }
 
 
+GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class) {
+	r_error = OK;
+	CodeGen codegen;
+	codegen.generator = memnew(GDScriptByteCodeGenerator);
+
+	codegen.class_node = p_class;
+	codegen.script = p_script;
+
+	StringName func_name = SNAME("@static_initializer");
+	bool is_static = true;
+	Variant rpc_config;
+	GDScriptDataType return_type;
+	return_type.has_type = true;
+	return_type.kind = GDScriptDataType::BUILTIN;
+	return_type.builtin_type = Variant::NIL;
+
+	codegen.function_name = func_name;
+	codegen.is_static = is_static;
+	codegen.generator->write_start(p_script, func_name, is_static, rpc_config, return_type);
+
+	GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS);
+
+	// Initialize the default values for typed variables before anything.
+	// This avoids crashes if they are accessed with validated calls before being properly initialized.
+	// It may happen with out-of-order access or with `@onready` variables.
+	for (const GDScriptParser::ClassNode::Member &member : p_class->members) {
+		if (member.type != GDScriptParser::ClassNode::Member::VARIABLE) {
+			continue;
+		}
+
+		const GDScriptParser::VariableNode *field = member.variable;
+		if (!field->is_static) {
+			continue;
+		}
+
+		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->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->pop_temporary();
+			}
+			// The `else` branch is for objects, in such case we leave it as `null`.
+		}
+	}
+
+	for (int i = 0; i < p_class->members.size(); i++) {
+		// Initialize static fields.
+		if (p_class->members[i].type != GDScriptParser::ClassNode::Member::VARIABLE) {
+			continue;
+		}
+		const GDScriptParser::VariableNode *field = p_class->members[i].variable;
+		if (!field->is_static) {
+			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);
+
+			GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, r_error, field->initializer, false, true);
+			if (r_error) {
+				memdelete(codegen.generator);
+				return nullptr;
+			}
+
+			GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
+			if (field->use_conversion_assign) {
+				codegen.generator->write_assign_with_conversion(temp, src_address);
+			} else {
+				codegen.generator->write_assign(temp, src_address);
+			}
+			if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+				codegen.generator->pop_temporary();
+			}
+
+			codegen.generator->write_set_named(class_addr, field->identifier->name, temp);
+			codegen.generator->pop_temporary();
+		}
+	}
+
+	if (p_script->has_method(GDScriptLanguage::get_singleton()->strings._static_init)) {
+		codegen.generator->write_newline(p_class->start_line);
+		codegen.generator->write_call(GDScriptCodeGenerator::Address(), class_addr, GDScriptLanguage::get_singleton()->strings._static_init, Vector<GDScriptCodeGenerator::Address>());
+	}
+
+#ifdef DEBUG_ENABLED
+	if (EngineDebugger::is_active()) {
+		String signature;
+		// Path.
+		if (!p_script->get_script_path().is_empty()) {
+			signature += p_script->get_script_path();
+		}
+		// Location.
+		signature += "::0";
+
+		// Function and class.
+
+		if (p_class->identifier) {
+			signature += "::" + String(p_class->identifier->name) + "." + String(func_name);
+		} else {
+			signature += "::" + String(func_name);
+		}
+
+		codegen.generator->set_signature(signature);
+	}
+#endif
+
+	codegen.generator->set_initial_line(p_class->start_line);
+
+	GDScriptFunction *gd_function = codegen.generator->write_end();
+
+	memdelete(codegen.generator);
+
+	return gd_function;
+}
+
 Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter) {
 Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter) {
 	Error err = OK;
 	Error err = OK;
 
 
@@ -2236,19 +2397,28 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
 	}
 	}
 	member_functions.clear();
 	member_functions.clear();
 
 
+	p_script->static_variables.clear();
+
 	if (p_script->implicit_initializer) {
 	if (p_script->implicit_initializer) {
 		memdelete(p_script->implicit_initializer);
 		memdelete(p_script->implicit_initializer);
 	}
 	}
 	if (p_script->implicit_ready) {
 	if (p_script->implicit_ready) {
 		memdelete(p_script->implicit_ready);
 		memdelete(p_script->implicit_ready);
 	}
 	}
+	if (p_script->static_initializer) {
+		memdelete(p_script->static_initializer);
+	}
+
 	p_script->member_functions.clear();
 	p_script->member_functions.clear();
 	p_script->member_indices.clear();
 	p_script->member_indices.clear();
 	p_script->member_info.clear();
 	p_script->member_info.clear();
+	p_script->static_variables_indices.clear();
+	p_script->static_variables.clear();
 	p_script->_signals.clear();
 	p_script->_signals.clear();
 	p_script->initializer = nullptr;
 	p_script->initializer = nullptr;
 	p_script->implicit_initializer = nullptr;
 	p_script->implicit_initializer = nullptr;
 	p_script->implicit_ready = nullptr;
 	p_script->implicit_ready = nullptr;
+	p_script->static_initializer = nullptr;
 
 
 	p_script->clearing = false;
 	p_script->clearing = false;
 
 
@@ -2357,9 +2527,14 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
 					prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE;
 					prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE;
 				}
 				}
 
 
-				p_script->member_info[name] = prop_info;
-				p_script->member_indices[name] = minfo;
-				p_script->members.insert(name);
+				if (variable->is_static) {
+					minfo.index = p_script->static_variables_indices.size();
+					p_script->static_variables_indices[name] = minfo;
+				} else {
+					p_script->member_info[name] = prop_info;
+					p_script->member_indices[name] = minfo;
+					p_script->members.insert(name);
+				}
 
 
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 				if (variable->initializer != nullptr && variable->initializer->is_constant) {
 				if (variable->initializer != nullptr && variable->initializer->is_constant) {
@@ -2427,6 +2602,8 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
 		}
 		}
 	}
 	}
 
 
+	p_script->static_variables.resize(p_script->static_variables_indices.size());
+
 	parsed_classes.insert(p_script);
 	parsed_classes.insert(p_script);
 	parsing_classes.erase(p_script);
 	parsing_classes.erase(p_script);
 
 
@@ -2503,6 +2680,15 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser:
 		}
 		}
 	}
 	}
 
 
+	if (p_class->has_static_data) {
+		Error err = OK;
+		GDScriptFunction *func = _make_static_initializer(err, p_script, p_class);
+		p_script->static_initializer = func;
+		if (err) {
+			return err;
+		}
+	}
+
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
 
 
 	//validate instances if keeping state
 	//validate instances if keeping state
@@ -2552,6 +2738,8 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser:
 	}
 	}
 #endif //DEBUG_ENABLED
 #endif //DEBUG_ENABLED
 
 
+	has_static_data = p_class->has_static_data;
+
 	for (int i = 0; i < p_class->members.size(); i++) {
 	for (int i = 0; i < p_class->members.size(); i++) {
 		if (p_class->members[i].type != GDScriptParser::ClassNode::Member::CLASS) {
 		if (p_class->members[i].type != GDScriptParser::ClassNode::Member::CLASS) {
 			continue;
 			continue;
@@ -2564,6 +2752,8 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser:
 		if (err) {
 		if (err) {
 			return err;
 			return err;
 		}
 		}
+
+		has_static_data = has_static_data || inner_class->has_static_data;
 	}
 	}
 
 
 	p_script->_init_rpc_methods_properties();
 	p_script->_init_rpc_methods_properties();
@@ -2650,6 +2840,10 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri
 		return err;
 		return err;
 	}
 	}
 
 
+	if (has_static_data && !root->annotated_static_unload) {
+		GDScriptCache::add_static_script(p_script);
+	}
+
 	return GDScriptCache::finish_compiling(main_script->get_path());
 	return GDScriptCache::finish_compiling(main_script->get_path());
 }
 }
 
 

+ 3 - 0
modules/gdscript/gdscript_compiler.h

@@ -52,6 +52,7 @@ class GDScriptCompiler {
 		HashMap<StringName, GDScriptCodeGenerator::Address> parameters;
 		HashMap<StringName, GDScriptCodeGenerator::Address> parameters;
 		HashMap<StringName, GDScriptCodeGenerator::Address> locals;
 		HashMap<StringName, GDScriptCodeGenerator::Address> locals;
 		List<HashMap<StringName, GDScriptCodeGenerator::Address>> locals_stack;
 		List<HashMap<StringName, GDScriptCodeGenerator::Address>> locals_stack;
+		bool is_static = false;
 
 
 		GDScriptCodeGenerator::Address add_local(const StringName &p_name, const GDScriptDataType &p_type) {
 		GDScriptCodeGenerator::Address add_local(const StringName &p_name, const GDScriptDataType &p_type) {
 			uint32_t addr = generator->add_local(p_name, p_type);
 			uint32_t addr = generator->add_local(p_name, p_type);
@@ -130,6 +131,7 @@ class GDScriptCompiler {
 	void _add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block);
 	void _add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block);
 	Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true);
 	Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true);
 	GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = false);
 	GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = false);
+	GDScriptFunction *_make_static_initializer(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class);
 	Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter);
 	Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter);
 	Error _populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
 	Error _populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
 	Error _compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
 	Error _compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
@@ -138,6 +140,7 @@ class GDScriptCompiler {
 	StringName source;
 	StringName source;
 	String error;
 	String error;
 	GDScriptParser::ExpressionNode *awaited_node = nullptr;
 	GDScriptParser::ExpressionNode *awaited_node = nullptr;
+	bool has_static_data = false;
 
 
 public:
 public:
 	static void convert_to_initializer_type(Variant &p_variant, const GDScriptParser::VariableNode *p_node);
 	static void convert_to_initializer_type(Variant &p_variant, const GDScriptParser::VariableNode *p_node);

+ 9 - 0
modules/gdscript/gdscript_editor.cpp

@@ -2992,6 +2992,15 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
 
 
 			List<MethodInfo> virtual_methods;
 			List<MethodInfo> virtual_methods;
 			ClassDB::get_virtual_methods(class_name, &virtual_methods);
 			ClassDB::get_virtual_methods(class_name, &virtual_methods);
+
+			{
+				// Not truly a virtual method, but can also be "overridden".
+				MethodInfo static_init("_static_init");
+				static_init.return_val.type = Variant::NIL;
+				static_init.flags |= METHOD_FLAG_STATIC | METHOD_FLAG_VIRTUAL;
+				virtual_methods.push_back(static_init);
+			}
+
 			for (const MethodInfo &mi : virtual_methods) {
 			for (const MethodInfo &mi : virtual_methods) {
 				String method_hint = mi.name;
 				String method_hint = mi.name;
 				if (method_hint.contains(":")) {
 				if (method_hint.contains(":")) {

+ 2 - 1
modules/gdscript/gdscript_function.h

@@ -409,7 +409,8 @@ public:
 		ADDR_TYPE_STACK = 0,
 		ADDR_TYPE_STACK = 0,
 		ADDR_TYPE_CONSTANT = 1,
 		ADDR_TYPE_CONSTANT = 1,
 		ADDR_TYPE_MEMBER = 2,
 		ADDR_TYPE_MEMBER = 2,
-		ADDR_TYPE_MAX = 3,
+		ADDR_TYPE_STATIC_VAR = 3,
+		ADDR_TYPE_MAX = 4,
 	};
 	};
 
 
 	enum FixedAddresses {
 	enum FixedAddresses {

+ 58 - 28
modules/gdscript/gdscript_parser.cpp

@@ -81,6 +81,8 @@ GDScriptParser::GDScriptParser() {
 	// TODO: Should this be static?
 	// TODO: Should this be static?
 	register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation);
 	register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation);
 	register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);
 	register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);
+	register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation);
+
 	register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
 	register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
 	// Export annotations.
 	// Export annotations.
 	register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>);
 	register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>);
@@ -623,7 +625,7 @@ bool GDScriptParser::has_class(const GDScriptParser::ClassNode *p_class) const {
 	return false;
 	return false;
 }
 }
 
 
-GDScriptParser::ClassNode *GDScriptParser::parse_class() {
+GDScriptParser::ClassNode *GDScriptParser::parse_class(bool p_is_static) {
 	ClassNode *n_class = alloc_node<ClassNode>();
 	ClassNode *n_class = alloc_node<ClassNode>();
 
 
 	ClassNode *previous_class = current_class;
 	ClassNode *previous_class = current_class;
@@ -724,7 +726,7 @@ void GDScriptParser::parse_extends() {
 }
 }
 
 
 template <class T>
 template <class T>
-void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind) {
+void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static) {
 	advance();
 	advance();
 
 
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
@@ -749,7 +751,7 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
 #endif // TOOLS_ENABLED
 #endif // TOOLS_ENABLED
 	}
 	}
 
 
-	T *member = (this->*p_parse_function)();
+	T *member = (this->*p_parse_function)(p_is_static);
 	if (member == nullptr) {
 	if (member == nullptr) {
 		return;
 		return;
 	}
 	}
@@ -803,10 +805,15 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
 
 
 void GDScriptParser::parse_class_body(bool p_is_multiline) {
 void GDScriptParser::parse_class_body(bool p_is_multiline) {
 	bool class_end = false;
 	bool class_end = false;
+	bool next_is_static = false;
 	while (!class_end && !is_at_end()) {
 	while (!class_end && !is_at_end()) {
-		switch (current.type) {
+		GDScriptTokenizer::Token token = current;
+		switch (token.type) {
 			case GDScriptTokenizer::Token::VAR:
 			case GDScriptTokenizer::Token::VAR:
-				parse_class_member(&GDScriptParser::parse_variable, AnnotationInfo::VARIABLE, "variable");
+				parse_class_member(&GDScriptParser::parse_variable, AnnotationInfo::VARIABLE, "variable", next_is_static);
+				if (next_is_static) {
+					current_class->has_static_data = true;
+				}
 				break;
 				break;
 			case GDScriptTokenizer::Token::CONST:
 			case GDScriptTokenizer::Token::CONST:
 				parse_class_member(&GDScriptParser::parse_constant, AnnotationInfo::CONSTANT, "constant");
 				parse_class_member(&GDScriptParser::parse_constant, AnnotationInfo::CONSTANT, "constant");
@@ -814,9 +821,8 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
 			case GDScriptTokenizer::Token::SIGNAL:
 			case GDScriptTokenizer::Token::SIGNAL:
 				parse_class_member(&GDScriptParser::parse_signal, AnnotationInfo::SIGNAL, "signal");
 				parse_class_member(&GDScriptParser::parse_signal, AnnotationInfo::SIGNAL, "signal");
 				break;
 				break;
-			case GDScriptTokenizer::Token::STATIC:
 			case GDScriptTokenizer::Token::FUNC:
 			case GDScriptTokenizer::Token::FUNC:
-				parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function");
+				parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function", next_is_static);
 				break;
 				break;
 			case GDScriptTokenizer::Token::CLASS:
 			case GDScriptTokenizer::Token::CLASS:
 				parse_class_member(&GDScriptParser::parse_class, AnnotationInfo::CLASS, "class");
 				parse_class_member(&GDScriptParser::parse_class, AnnotationInfo::CLASS, "class");
@@ -824,6 +830,13 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
 			case GDScriptTokenizer::Token::ENUM:
 			case GDScriptTokenizer::Token::ENUM:
 				parse_class_member(&GDScriptParser::parse_enum, AnnotationInfo::NONE, "enum");
 				parse_class_member(&GDScriptParser::parse_enum, AnnotationInfo::NONE, "enum");
 				break;
 				break;
+			case GDScriptTokenizer::Token::STATIC: {
+				advance();
+				next_is_static = true;
+				if (!check(GDScriptTokenizer::Token::FUNC) && !check(GDScriptTokenizer::Token::VAR)) {
+					push_error(R"(Expected "func" or "var" after "static".)");
+				}
+			} break;
 			case GDScriptTokenizer::Token::ANNOTATION: {
 			case GDScriptTokenizer::Token::ANNOTATION: {
 				advance();
 				advance();
 
 
@@ -870,6 +883,9 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
 				advance();
 				advance();
 				break;
 				break;
 		}
 		}
+		if (token.type != GDScriptTokenizer::Token::STATIC) {
+			next_is_static = false;
+		}
 		if (panic_mode) {
 		if (panic_mode) {
 			synchronize();
 			synchronize();
 		}
 		}
@@ -879,11 +895,11 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
 	}
 	}
 }
 }
 
 
-GDScriptParser::VariableNode *GDScriptParser::parse_variable() {
-	return parse_variable(true);
+GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static) {
+	return parse_variable(p_is_static, true);
 }
 }
 
 
-GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_property) {
+GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, bool p_allow_property) {
 	VariableNode *variable = alloc_node<VariableNode>();
 	VariableNode *variable = alloc_node<VariableNode>();
 
 
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) {
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) {
@@ -893,6 +909,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper
 
 
 	variable->identifier = parse_identifier();
 	variable->identifier = parse_identifier();
 	variable->export_info.name = variable->identifier->name;
 	variable->export_info.name = variable->identifier->name;
+	variable->is_static = p_is_static;
 
 
 	if (match(GDScriptTokenizer::Token::COLON)) {
 	if (match(GDScriptTokenizer::Token::COLON)) {
 		if (check(GDScriptTokenizer::Token::NEWLINE)) {
 		if (check(GDScriptTokenizer::Token::NEWLINE)) {
@@ -1036,6 +1053,7 @@ void GDScriptParser::parse_property_setter(VariableNode *p_variable) {
 			complete_extents(identifier);
 			complete_extents(identifier);
 			identifier->name = "@" + p_variable->identifier->name + "_setter";
 			identifier->name = "@" + p_variable->identifier->name + "_setter";
 			function->identifier = identifier;
 			function->identifier = identifier;
+			function->is_static = p_variable->is_static;
 
 
 			consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "set".)");
 			consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "set".)");
 
 
@@ -1087,6 +1105,7 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
 			complete_extents(identifier);
 			complete_extents(identifier);
 			identifier->name = "@" + p_variable->identifier->name + "_getter";
 			identifier->name = "@" + p_variable->identifier->name + "_getter";
 			function->identifier = identifier;
 			function->identifier = identifier;
+			function->is_static = p_variable->is_static;
 
 
 			FunctionNode *previous_function = current_function;
 			FunctionNode *previous_function = current_function;
 			current_function = function;
 			current_function = function;
@@ -1111,7 +1130,7 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
 	}
 	}
 }
 }
 
 
-GDScriptParser::ConstantNode *GDScriptParser::parse_constant() {
+GDScriptParser::ConstantNode *GDScriptParser::parse_constant(bool p_is_static) {
 	ConstantNode *constant = alloc_node<ConstantNode>();
 	ConstantNode *constant = alloc_node<ConstantNode>();
 
 
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) {
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) {
@@ -1178,7 +1197,7 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() {
 	return parameter;
 	return parameter;
 }
 }
 
 
-GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
+GDScriptParser::SignalNode *GDScriptParser::parse_signal(bool p_is_static) {
 	SignalNode *signal = alloc_node<SignalNode>();
 	SignalNode *signal = alloc_node<SignalNode>();
 
 
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected signal name after "signal".)")) {
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected signal name after "signal".)")) {
@@ -1223,7 +1242,7 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
 	return signal;
 	return signal;
 }
 }
 
 
-GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
+GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
 	EnumNode *enum_node = alloc_node<EnumNode>();
 	EnumNode *enum_node = alloc_node<EnumNode>();
 	bool named = false;
 	bool named = false;
 
 
@@ -1372,23 +1391,23 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod
 		}
 		}
 	}
 	}
 
 
+	if (!p_function->source_lambda && p_function->identifier && p_function->identifier->name == GDScriptLanguage::get_singleton()->strings._static_init) {
+		if (!p_function->is_static) {
+			push_error(R"(Static constructor must be declared static.)");
+		}
+		if (p_function->parameters.size() != 0) {
+			push_error(R"(Static constructor cannot have parameters.)");
+		}
+		current_class->has_static_data = true;
+	}
+
 	// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
 	// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
 	consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after %s declaration.)", p_type));
 	consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after %s declaration.)", p_type));
 }
 }
 
 
-GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
+GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_static) {
 	FunctionNode *function = alloc_node<FunctionNode>();
 	FunctionNode *function = alloc_node<FunctionNode>();
 
 
-	bool _static = false;
-	if (previous.type == GDScriptTokenizer::Token::STATIC) {
-		// TODO: Improve message if user uses "static" with "var" or "const"
-		if (!consume(GDScriptTokenizer::Token::FUNC, R"(Expected "func" after "static".)")) {
-			complete_extents(function);
-			return nullptr;
-		}
-		_static = true;
-	}
-
 	make_completion_context(COMPLETION_OVERRIDE_METHOD, function);
 	make_completion_context(COMPLETION_OVERRIDE_METHOD, function);
 
 
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) {
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) {
@@ -1400,7 +1419,7 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
 	current_function = function;
 	current_function = function;
 
 
 	function->identifier = parse_identifier();
 	function->identifier = parse_identifier();
-	function->is_static = _static;
+	function->is_static = p_is_static;
 
 
 	SuiteNode *body = alloc_node<SuiteNode>();
 	SuiteNode *body = alloc_node<SuiteNode>();
 	SuiteNode *previous_suite = current_suite;
 	SuiteNode *previous_suite = current_suite;
@@ -1612,11 +1631,11 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
 			break;
 			break;
 		case GDScriptTokenizer::Token::VAR:
 		case GDScriptTokenizer::Token::VAR:
 			advance();
 			advance();
-			result = parse_variable();
+			result = parse_variable(false, false);
 			break;
 			break;
 		case GDScriptTokenizer::Token::CONST:
 		case GDScriptTokenizer::Token::CONST:
 			advance();
 			advance();
-			result = parse_constant();
+			result = parse_constant(false);
 			break;
 			break;
 		case GDScriptTokenizer::Token::IF:
 		case GDScriptTokenizer::Token::IF:
 			advance();
 			advance();
@@ -1646,7 +1665,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
 			advance();
 			advance();
 			ReturnNode *n_return = alloc_node<ReturnNode>();
 			ReturnNode *n_return = alloc_node<ReturnNode>();
 			if (!is_statement_end()) {
 			if (!is_statement_end()) {
-				if (current_function && current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) {
+				if (current_function && (current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init || current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._static_init)) {
 					push_error(R"(Constructor cannot return a value.)");
 					push_error(R"(Constructor cannot return a value.)");
 				}
 				}
 				n_return->return_value = parse_expression(false);
 				n_return->return_value = parse_expression(false);
@@ -4101,6 +4120,17 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_
 	return true;
 	return true;
 }
 }
 
 
+bool GDScriptParser::static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target) {
+	ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, vformat(R"("%s" annotation can only be applied to classes.)", p_annotation->name));
+	ClassNode *p_class = static_cast<ClassNode *>(p_target);
+	if (p_class->annotated_static_unload) {
+		push_error(vformat(R"("%s" annotation can only be used once per script.)", p_annotation->name), p_annotation);
+		return false;
+	}
+	p_class->annotated_static_unload = true;
+	return true;
+}
+
 GDScriptParser::DataType GDScriptParser::SuiteNode::Local::get_datatype() const {
 GDScriptParser::DataType GDScriptParser::SuiteNode::Local::get_datatype() const {
 	switch (type) {
 	switch (type) {
 		case CONSTANT:
 		case CONSTANT:

+ 13 - 8
modules/gdscript/gdscript_parser.h

@@ -709,6 +709,8 @@ public:
 		ClassNode *outer = nullptr;
 		ClassNode *outer = nullptr;
 		bool extends_used = false;
 		bool extends_used = false;
 		bool onready_used = false;
 		bool onready_used = false;
+		bool has_static_data = false;
+		bool annotated_static_unload = false;
 		String extends_path;
 		String extends_path;
 		Vector<IdentifierNode *> extends; // List for indexing: extends A.B.C
 		Vector<IdentifierNode *> extends; // List for indexing: extends A.B.C
 		DataType base_type;
 		DataType base_type;
@@ -847,6 +849,7 @@ public:
 			LOCAL_BIND, // Pattern bind.
 			LOCAL_BIND, // Pattern bind.
 			MEMBER_SIGNAL,
 			MEMBER_SIGNAL,
 			MEMBER_VARIABLE,
 			MEMBER_VARIABLE,
+			STATIC_VARIABLE,
 			MEMBER_CONSTANT,
 			MEMBER_CONSTANT,
 			INHERITED_VARIABLE,
 			INHERITED_VARIABLE,
 		};
 		};
@@ -1202,6 +1205,7 @@ public:
 		bool onready = false;
 		bool onready = false;
 		PropertyInfo export_info;
 		PropertyInfo export_info;
 		int assignments = 0;
 		int assignments = 0;
+		bool is_static = false;
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 		String doc_description;
 		String doc_description;
 #endif // TOOLS_ENABLED
 #endif // TOOLS_ENABLED
@@ -1405,16 +1409,16 @@ private:
 
 
 	// Main blocks.
 	// Main blocks.
 	void parse_program();
 	void parse_program();
-	ClassNode *parse_class();
+	ClassNode *parse_class(bool p_is_static);
 	void parse_class_name();
 	void parse_class_name();
 	void parse_extends();
 	void parse_extends();
 	void parse_class_body(bool p_is_multiline);
 	void parse_class_body(bool p_is_multiline);
 	template <class T>
 	template <class T>
-	void parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind);
-	SignalNode *parse_signal();
-	EnumNode *parse_enum();
+	void parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static = false);
+	SignalNode *parse_signal(bool p_is_static);
+	EnumNode *parse_enum(bool p_is_static);
 	ParameterNode *parse_parameter();
 	ParameterNode *parse_parameter();
-	FunctionNode *parse_function();
+	FunctionNode *parse_function(bool p_is_static);
 	void parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type);
 	void parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type);
 	SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr, bool p_for_lambda = false);
 	SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr, bool p_for_lambda = false);
 	// Annotations
 	// Annotations
@@ -1431,14 +1435,15 @@ private:
 	bool export_group_annotations(const AnnotationNode *p_annotation, Node *p_target);
 	bool export_group_annotations(const AnnotationNode *p_annotation, Node *p_target);
 	bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target);
 	bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target);
 	bool rpc_annotation(const AnnotationNode *p_annotation, Node *p_target);
 	bool rpc_annotation(const AnnotationNode *p_annotation, Node *p_target);
+	bool static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target);
 	// Statements.
 	// Statements.
 	Node *parse_statement();
 	Node *parse_statement();
-	VariableNode *parse_variable();
-	VariableNode *parse_variable(bool p_allow_property);
+	VariableNode *parse_variable(bool p_is_static);
+	VariableNode *parse_variable(bool p_is_static, bool p_allow_property);
 	VariableNode *parse_property(VariableNode *p_variable, bool p_need_indent);
 	VariableNode *parse_property(VariableNode *p_variable, bool p_need_indent);
 	void parse_property_getter(VariableNode *p_variable);
 	void parse_property_getter(VariableNode *p_variable);
 	void parse_property_setter(VariableNode *p_variable);
 	void parse_property_setter(VariableNode *p_variable);
-	ConstantNode *parse_constant();
+	ConstantNode *parse_constant(bool p_is_static);
 	AssertNode *parse_assert();
 	AssertNode *parse_assert();
 	BreakNode *parse_break();
 	BreakNode *parse_break();
 	ContinueNode *parse_continue();
 	ContinueNode *parse_continue();

+ 2 - 2
modules/gdscript/gdscript_vm.cpp

@@ -680,10 +680,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 	bool awaited = false;
 	bool awaited = false;
 #endif
 #endif
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
-	int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0 };
+	int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0, script->static_variables.size() };
 #endif
 #endif
 
 
-	Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr };
+	Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr, script->static_variables.ptrw() };
 
 
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
 	OPCODE_WHILE(ip < _code_size) {
 	OPCODE_WHILE(ip < _code_size) {

+ 4 - 0
modules/gdscript/gdscript_warning.cpp

@@ -185,6 +185,9 @@ String GDScriptWarning::get_message() const {
 		case ONREADY_WITH_EXPORT: {
 		case ONREADY_WITH_EXPORT: {
 			return R"("@onready" will set the default value after "@export" takes effect and will override it.)";
 			return R"("@onready" will set the default value after "@export" takes effect and will override it.)";
 		}
 		}
+		case REDUNDANT_STATIC_UNLOAD: {
+			return R"(The "@static_unload" annotation is redundant because the file does not have a class with static variables.)";
+		}
 		case WARNING_MAX:
 		case WARNING_MAX:
 			break; // Can't happen, but silences warning
 			break; // Can't happen, but silences warning
 	}
 	}
@@ -254,6 +257,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
 		"NATIVE_METHOD_OVERRIDE",
 		"NATIVE_METHOD_OVERRIDE",
 		"GET_NODE_DEFAULT_WITHOUT_ONREADY",
 		"GET_NODE_DEFAULT_WITHOUT_ONREADY",
 		"ONREADY_WITH_EXPORT",
 		"ONREADY_WITH_EXPORT",
+		"REDUNDANT_STATIC_UNLOAD",
 	};
 	};
 
 
 	static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");
 	static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");

+ 2 - 0
modules/gdscript/gdscript_warning.h

@@ -86,6 +86,7 @@ public:
 		NATIVE_METHOD_OVERRIDE, // The script method overrides a native one, this may not work as intended.
 		NATIVE_METHOD_OVERRIDE, // The script method overrides a native one, this may not work as intended.
 		GET_NODE_DEFAULT_WITHOUT_ONREADY, // A class variable uses `get_node()` (or the `$` notation) as its default value, but does not use the @onready annotation.
 		GET_NODE_DEFAULT_WITHOUT_ONREADY, // A class variable uses `get_node()` (or the `$` notation) as its default value, but does not use the @onready annotation.
 		ONREADY_WITH_EXPORT, // The `@onready` annotation will set the value after `@export` which is likely not intended.
 		ONREADY_WITH_EXPORT, // The `@onready` annotation will set the value after `@export` which is likely not intended.
+		REDUNDANT_STATIC_UNLOAD, // The `@static_unload` annotation is used but the class does not have static data.
 		WARNING_MAX,
 		WARNING_MAX,
 	};
 	};
 
 
@@ -130,6 +131,7 @@ public:
 		ERROR, // NATIVE_METHOD_OVERRIDE // May not work as expected.
 		ERROR, // NATIVE_METHOD_OVERRIDE // May not work as expected.
 		ERROR, // GET_NODE_DEFAULT_WITHOUT_ONREADY // May not work as expected.
 		ERROR, // GET_NODE_DEFAULT_WITHOUT_ONREADY // May not work as expected.
 		ERROR, // ONREADY_WITH_EXPORT // May not work as expected.
 		ERROR, // ONREADY_WITH_EXPORT // May not work as expected.
+		WARN, // REDUNDANT_STATIC_UNLOAD
 	};
 	};
 
 
 	static_assert((sizeof(default_warning_levels) / sizeof(default_warning_levels[0])) == WARNING_MAX, "Amount of default levels does not match the amount of warnings.");
 	static_assert((sizeof(default_warning_levels) / sizeof(default_warning_levels[0])) == WARNING_MAX, "Amount of default levels does not match the amount of warnings.");

+ 8 - 8
modules/gdscript/tests/gdscript_test_runner.cpp

@@ -566,6 +566,14 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
 		ERR_FAIL_V_MSG(result, "\nCould not find test function on: '" + source_file + "'");
 		ERR_FAIL_V_MSG(result, "\nCould not find test function on: '" + source_file + "'");
 	}
 	}
 
 
+	// Setup output handlers.
+	ErrorHandlerData error_data(&result, this);
+
+	_print_handler.userdata = &result;
+	_error_handler.userdata = &error_data;
+	add_print_handler(&_print_handler);
+	add_error_handler(&_error_handler);
+
 	script->reload();
 	script->reload();
 
 
 	// Create object instance for test.
 	// Create object instance for test.
@@ -577,14 +585,6 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
 	obj->set_script(script);
 	obj->set_script(script);
 	GDScriptInstance *instance = static_cast<GDScriptInstance *>(obj->get_script_instance());
 	GDScriptInstance *instance = static_cast<GDScriptInstance *>(obj->get_script_instance());
 
 
-	// Setup output handlers.
-	ErrorHandlerData error_data(&result, this);
-
-	_print_handler.userdata = &result;
-	_error_handler.userdata = &error_data;
-	add_print_handler(&_print_handler);
-	add_error_handler(&_error_handler);
-
 	// Call test function.
 	// Call test function.
 	Callable::CallError call_err;
 	Callable::CallError call_err;
 	instance->callp(GDScriptTestRunner::test_function_name, nullptr, 0, call_err);
 	instance->callp(GDScriptTestRunner::test_function_name, nullptr, 0, call_err);

+ 5 - 0
modules/gdscript/tests/scripts/analyzer/errors/static_constructor_with_return_type.gd

@@ -0,0 +1,5 @@
+static func _static_init() -> int:
+	print("static init")
+
+func test():
+	print("done")

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/errors/static_constructor_with_return_type.out

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Static constructor cannot have an explicit return type.

+ 9 - 0
modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.gd

@@ -0,0 +1,9 @@
+@static_unload
+
+func non_static():
+	return "non static"
+
+static var static_var = non_static()
+
+func test():
+	print("does not run")

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot call non-static function "non_static()" for static variable initializer.

+ 5 - 0
modules/gdscript/tests/scripts/parser/errors/static_constructor_not_static.gd

@@ -0,0 +1,5 @@
+func _static_init():
+	print("static init")
+
+func test():
+	print("done")

+ 2 - 0
modules/gdscript/tests/scripts/parser/errors/static_constructor_not_static.out

@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Static constructor must be declared static.

+ 6 - 0
modules/gdscript/tests/scripts/parser/errors/static_constructor_returning_something.gd

@@ -0,0 +1,6 @@
+static func _static_init():
+	print("static init")
+	return true
+
+func test():
+	print("done")

+ 2 - 0
modules/gdscript/tests/scripts/parser/errors/static_constructor_returning_something.out

@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Constructor cannot return a value.

+ 13 - 0
modules/gdscript/tests/scripts/runtime/features/static_constructor.gd

@@ -0,0 +1,13 @@
+@static_unload
+
+static var foo = "bar"
+
+static func _static_init():
+	print("static init called")
+	prints("foo is", foo)
+
+func test():
+	var _lambda = func _static_init():
+		print("lambda does not conflict with static constructor")
+
+	print("done")

+ 4 - 0
modules/gdscript/tests/scripts/runtime/features/static_constructor.out

@@ -0,0 +1,4 @@
+GDTEST_OK
+static init called
+foo is bar
+done

+ 56 - 0
modules/gdscript/tests/scripts/runtime/features/static_variables.gd

@@ -0,0 +1,56 @@
+@static_unload
+
+static var perm := 0
+
+static var prop := "Hello!":
+	get: return prop + " suffix"
+	set(value): prop = "prefix " + str(value)
+
+static func get_data():
+	return "data"
+
+static var data = get_data()
+
+class Inner:
+	static var prop := "inner"
+	static func _static_init() -> void:
+		prints("Inner._static_init", prop)
+
+	class InnerInner:
+		static var prop := "inner inner"
+		static func _static_init() -> void:
+			prints("InnerInner._static_init", prop)
+
+func test():
+	prints("data:", data)
+
+	prints("perm:", perm)
+	prints("prop:", prop)
+
+	perm = 1
+	prop = "World!"
+
+	prints("perm:", perm)
+	prints("prop:", prop)
+
+	print("other.perm:", StaticVariablesOther.perm)
+	print("other.prop:", StaticVariablesOther.prop)
+
+	StaticVariablesOther.perm = 2
+	StaticVariablesOther.prop = "foo"
+
+	print("other.perm:", StaticVariablesOther.perm)
+	print("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")
+
+	print("load.perm:", other.perm)
+	print("load.prop:", other.prop)
+
+	other.perm = 3
+	other.prop = "bar"
+
+	print("load.perm:", other.perm)
+	print("load.prop:", other.prop)

+ 16 - 0
modules/gdscript/tests/scripts/runtime/features/static_variables.out

@@ -0,0 +1,16 @@
+GDTEST_OK
+Inner._static_init inner
+InnerInner._static_init inner inner
+data: data
+perm: 0
+prop: prefix 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

+ 10 - 0
modules/gdscript/tests/scripts/runtime/features/static_variables_load.gd

@@ -0,0 +1,10 @@
+@static_unload
+
+static var perm := 0
+
+static var prop := "Hello!":
+	get: return prop + " suffix"
+	set(value): prop = "prefix " + str(value)
+
+func test():
+	print("ok")

+ 2 - 0
modules/gdscript/tests/scripts/runtime/features/static_variables_load.out

@@ -0,0 +1,2 @@
+GDTEST_OK
+ok

+ 11 - 0
modules/gdscript/tests/scripts/runtime/features/static_variables_other.gd

@@ -0,0 +1,11 @@
+@static_unload
+class_name StaticVariablesOther
+
+static var perm := 0
+
+static var prop := "Hello!":
+	get: return prop + " suffix"
+	set(value): prop = "prefix " + str(value)
+
+func test():
+	print("ok")

+ 2 - 0
modules/gdscript/tests/scripts/runtime/features/static_variables_other.out

@@ -0,0 +1,2 @@
+GDTEST_OK
+ok