1
0
mirror of https://github.com/godotengine/godot synced 2024-07-08 16:30:44 +00:00

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.
This commit is contained in:
George Marques 2023-04-19 11:10:35 -03:00
parent 352ebe9725
commit 0ba6048ad3
No known key found for this signature in database
GPG Key ID: 046BD46A3201E43D
36 changed files with 689 additions and 86 deletions

View File

@ -456,6 +456,9 @@
<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.
</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">
When enabled, using a property, enum, or function that was renamed since Godot 3 will produce a hint if an error occurs.
</member>

View File

@ -622,6 +622,12 @@
[/codeblock]
</description>
</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">
<return type="void" />
<description>

View File

@ -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) {
if (reloading) {
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;
GDScriptParser parser;
Error err = parser.parse(source, path, false);
@ -726,7 +777,7 @@ Error GDScript::reload(bool p_keep_state) {
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;
err = compiler.compile(&parser, this, p_keep_state);
@ -760,6 +811,19 @@ Error GDScript::reload(bool p_keep_state) {
}
#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;
return OK;
}
@ -788,6 +852,10 @@ const Variant GDScript::get_rpc_config() const {
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) {
GDScript *top = this;
while (top) {
@ -824,6 +892,19 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const {
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;
}
@ -841,7 +922,32 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) {
set_source_code(p_value);
reload();
} 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;
@ -1293,6 +1399,13 @@ void GDScript::clear(GDScript::ClearData *p_clear_data) {
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) {
clear_data->functions.insert(implicit_initializer);
implicit_initializer = nullptr;
@ -1303,6 +1416,11 @@ void GDScript::clear(GDScript::ClearData *p_clear_data) {
implicit_ready = nullptr;
}
if (static_initializer) {
clear_data->functions.insert(static_initializer);
static_initializer = nullptr;
}
_save_orphaned_subclasses(clear_data);
#ifdef TOOLS_ENABLED
@ -1357,10 +1475,6 @@ GDScript::~GDScript() {
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);
singleton = this;
strings._init = StaticCString::create("_init");
strings._static_init = StaticCString::create("_static_init");
strings._notification = StaticCString::create("_notification");
strings._set = StaticCString::create("_set");
strings._get = StaticCString::create("_get");

View File

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

View File

@ -879,6 +879,8 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
#endif
switch (member.type) {
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);
member.variable->set_datatype(resolving_datatype);
resolve_variable(member.variable, false);
@ -890,6 +892,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
E->apply(parser, member.variable);
}
}
static_context = previous_static_context;
#ifdef DEBUG_ENABLED
if (member.variable->exported && member.variable->onready) {
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) {
// 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.
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;
if (expr->type == GDScriptParser::Node::CAST) {
expr = static_cast<GDScriptParser::CastNode *>(expr)->operand;
@ -1082,6 +1085,10 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
p_source = p_class;
}
#ifdef DEBUG_ENABLED
bool has_static_data = p_class->has_static_data;
#endif
if (!p_class->resolved_interface) {
if (!parser->has_class(p_class)) {
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++) {
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;
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();
@ -1542,6 +1573,18 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
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 {
if (p_function->return_type != nullptr) {
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;
#endif
parser->current_function = previous_function;
static_context = previous_static_context;
}
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;
}
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) {
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);
@ -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);
}
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);
if (caller_type.is_empty()) {
@ -3428,9 +3476,9 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
}
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->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;
member.variable->usages += 1;
return;
@ -3572,6 +3620,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
mark_lambda_use_self();
p_identifier->variable_source->usages++;
[[fallthrough]];
case GDScriptParser::IdentifierNode::STATIC_VARIABLE:
case GDScriptParser::IdentifierNode::LOCAL_VARIABLE:
p_identifier->set_datatype(p_identifier->variable_source->get_datatype());
found_source = true;
@ -3602,13 +3651,17 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
if (found_source) {
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;
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()) {

View File

@ -43,6 +43,7 @@ class GDScriptAnalyzer {
const GDScriptParser::EnumNode *current_enum = nullptr;
List<GDScriptParser::LambdaNode *> lambda_stack;
bool static_context = false;
// 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);

View File

@ -366,6 +366,8 @@ 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);

View File

@ -342,6 +342,16 @@ Error GDScriptCache::finish_compiling(const String &p_owner) {
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) {
MutexLock lock(singleton->mutex);

View File

@ -78,6 +78,7 @@ class GDScriptCache {
HashMap<String, GDScriptParserRef *> parser_map;
HashMap<String, Ref<GDScript>> shallow_gdscript_cache;
HashMap<String, Ref<GDScript>> full_gdscript_cache;
HashMap<String, Ref<GDScript>> static_gdscript_cache;
HashMap<String, HashSet<String>> dependencies;
HashMap<String, Ref<PackedScene>> packed_scene_cache;
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_cached_script(const String &p_path);
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 void clear_unreferenced_packed_scenes();

View File

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

View File

@ -254,13 +254,29 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
gen->write_call_self(temp, codegen.script->member_indices[identifier].getter, args);
return temp;
} 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;
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.
{
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.
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;
self.mode = GDScriptCodeGenerator::Address::CLASS;
if (is_awaited) {
@ -909,6 +925,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
bool is_member_property = false;
bool member_property_has_setter = false;
bool member_property_is_in_setter = false;
bool is_static = false;
StringName member_property_setter_function;
List<const GDScriptParser::SubscriptNode *> chain;
@ -925,14 +942,16 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
StringName 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)) {
} 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;
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_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;
@ -1085,7 +1104,8 @@ 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);
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 {
gen->write_assign(target_member_property, assigned);
}
@ -1134,16 +1154,19 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
bool is_member = false;
bool has_setter = false;
bool is_in_setter = false;
bool is_static = false;
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)) {
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;
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();
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;
@ -2001,6 +2024,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
}
codegen.function_name = func_name;
codegen.is_static = is_static;
codegen.generator->write_start(p_script, func_name, is_static, rpc_config, return_type);
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;
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.
// It may happen with out-of-order access or with `@onready` variables.
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;
if (field->is_static) {
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);
@ -2056,6 +2084,10 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
continue;
}
const GDScriptParser::VariableNode *field = p_class->members[i].variable;
if (field->is_static) {
continue;
}
if (field->onready != is_implicit_ready) {
// Only initialize in @implicit_ready.
continue;
@ -2183,6 +2215,135 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
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 err = OK;
@ -2236,19 +2397,28 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
}
member_functions.clear();
p_script->static_variables.clear();
if (p_script->implicit_initializer) {
memdelete(p_script->implicit_initializer);
}
if (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_indices.clear();
p_script->member_info.clear();
p_script->static_variables_indices.clear();
p_script->static_variables.clear();
p_script->_signals.clear();
p_script->initializer = nullptr;
p_script->implicit_initializer = nullptr;
p_script->implicit_ready = nullptr;
p_script->static_initializer = nullptr;
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;
}
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
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);
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
//validate instances if keeping state
@ -2552,6 +2738,8 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser:
}
#endif //DEBUG_ENABLED
has_static_data = p_class->has_static_data;
for (int i = 0; i < p_class->members.size(); i++) {
if (p_class->members[i].type != GDScriptParser::ClassNode::Member::CLASS) {
continue;
@ -2564,6 +2752,8 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser:
if (err) {
return err;
}
has_static_data = has_static_data || inner_class->has_static_data;
}
p_script->_init_rpc_methods_properties();
@ -2650,6 +2840,10 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri
return err;
}
if (has_static_data && !root->annotated_static_unload) {
GDScriptCache::add_static_script(p_script);
}
return GDScriptCache::finish_compiling(main_script->get_path());
}

View File

@ -52,6 +52,7 @@ class GDScriptCompiler {
HashMap<StringName, GDScriptCodeGenerator::Address> parameters;
HashMap<StringName, GDScriptCodeGenerator::Address> locals;
List<HashMap<StringName, GDScriptCodeGenerator::Address>> locals_stack;
bool is_static = false;
GDScriptCodeGenerator::Address add_local(const StringName &p_name, const GDScriptDataType &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);
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 *_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 _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);
@ -138,6 +140,7 @@ class GDScriptCompiler {
StringName source;
String error;
GDScriptParser::ExpressionNode *awaited_node = nullptr;
bool has_static_data = false;
public:
static void convert_to_initializer_type(Variant &p_variant, const GDScriptParser::VariableNode *p_node);

View File

@ -2992,6 +2992,15 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
List<MethodInfo> 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) {
String method_hint = mi.name;
if (method_hint.contains(":")) {

View File

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

View File

@ -81,6 +81,8 @@ GDScriptParser::GDScriptParser() {
// TODO: Should this be static?
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("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation);
register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
// Export annotations.
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;
}
GDScriptParser::ClassNode *GDScriptParser::parse_class() {
GDScriptParser::ClassNode *GDScriptParser::parse_class(bool p_is_static) {
ClassNode *n_class = alloc_node<ClassNode>();
ClassNode *previous_class = current_class;
@ -724,7 +726,7 @@ void GDScriptParser::parse_extends() {
}
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();
#ifdef TOOLS_ENABLED
@ -749,7 +751,7 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
#endif // TOOLS_ENABLED
}
T *member = (this->*p_parse_function)();
T *member = (this->*p_parse_function)(p_is_static);
if (member == nullptr) {
return;
}
@ -803,10 +805,15 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
void GDScriptParser::parse_class_body(bool p_is_multiline) {
bool class_end = false;
bool next_is_static = false;
while (!class_end && !is_at_end()) {
switch (current.type) {
GDScriptTokenizer::Token token = current;
switch (token.type) {
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;
case GDScriptTokenizer::Token::CONST:
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:
parse_class_member(&GDScriptParser::parse_signal, AnnotationInfo::SIGNAL, "signal");
break;
case GDScriptTokenizer::Token::STATIC:
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;
case GDScriptTokenizer::Token::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:
parse_class_member(&GDScriptParser::parse_enum, AnnotationInfo::NONE, "enum");
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: {
advance();
@ -870,6 +883,9 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
advance();
break;
}
if (token.type != GDScriptTokenizer::Token::STATIC) {
next_is_static = false;
}
if (panic_mode) {
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>();
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->export_info.name = variable->identifier->name;
variable->is_static = p_is_static;
if (match(GDScriptTokenizer::Token::COLON)) {
if (check(GDScriptTokenizer::Token::NEWLINE)) {
@ -1036,6 +1053,7 @@ void GDScriptParser::parse_property_setter(VariableNode *p_variable) {
complete_extents(identifier);
identifier->name = "@" + p_variable->identifier->name + "_setter";
function->identifier = identifier;
function->is_static = p_variable->is_static;
consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "set".)");
@ -1087,6 +1105,7 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
complete_extents(identifier);
identifier->name = "@" + p_variable->identifier->name + "_getter";
function->identifier = identifier;
function->is_static = p_variable->is_static;
FunctionNode *previous_function = current_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>();
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) {
@ -1178,7 +1197,7 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() {
return parameter;
}
GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
GDScriptParser::SignalNode *GDScriptParser::parse_signal(bool p_is_static) {
SignalNode *signal = alloc_node<SignalNode>();
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected signal name after "signal".)")) {
@ -1223,7 +1242,7 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
return signal;
}
GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
EnumNode *enum_node = alloc_node<EnumNode>();
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.
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>();
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);
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) {
@ -1400,7 +1419,7 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
current_function = function;
function->identifier = parse_identifier();
function->is_static = _static;
function->is_static = p_is_static;
SuiteNode *body = alloc_node<SuiteNode>();
SuiteNode *previous_suite = current_suite;
@ -1612,11 +1631,11 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
break;
case GDScriptTokenizer::Token::VAR:
advance();
result = parse_variable();
result = parse_variable(false, false);
break;
case GDScriptTokenizer::Token::CONST:
advance();
result = parse_constant();
result = parse_constant(false);
break;
case GDScriptTokenizer::Token::IF:
advance();
@ -1646,7 +1665,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
advance();
ReturnNode *n_return = alloc_node<ReturnNode>();
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.)");
}
n_return->return_value = parse_expression(false);
@ -4101,6 +4120,17 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_
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 {
switch (type) {
case CONSTANT:

View File

@ -709,6 +709,8 @@ public:
ClassNode *outer = nullptr;
bool extends_used = false;
bool onready_used = false;
bool has_static_data = false;
bool annotated_static_unload = false;
String extends_path;
Vector<IdentifierNode *> extends; // List for indexing: extends A.B.C
DataType base_type;
@ -847,6 +849,7 @@ public:
LOCAL_BIND, // Pattern bind.
MEMBER_SIGNAL,
MEMBER_VARIABLE,
STATIC_VARIABLE,
MEMBER_CONSTANT,
INHERITED_VARIABLE,
};
@ -1202,6 +1205,7 @@ public:
bool onready = false;
PropertyInfo export_info;
int assignments = 0;
bool is_static = false;
#ifdef TOOLS_ENABLED
String doc_description;
#endif // TOOLS_ENABLED
@ -1405,16 +1409,16 @@ private:
// Main blocks.
void parse_program();
ClassNode *parse_class();
ClassNode *parse_class(bool p_is_static);
void parse_class_name();
void parse_extends();
void parse_class_body(bool p_is_multiline);
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();
FunctionNode *parse_function();
FunctionNode *parse_function(bool p_is_static);
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);
// Annotations
@ -1431,14 +1435,15 @@ private:
bool export_group_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 static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target);
// Statements.
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);
void parse_property_getter(VariableNode *p_variable);
void parse_property_setter(VariableNode *p_variable);
ConstantNode *parse_constant();
ConstantNode *parse_constant(bool p_is_static);
AssertNode *parse_assert();
BreakNode *parse_break();
ContinueNode *parse_continue();

View File

@ -680,10 +680,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
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 };
int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0, script->static_variables.size() };
#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
OPCODE_WHILE(ip < _code_size) {

View File

@ -185,6 +185,9 @@ String GDScriptWarning::get_message() const {
case ONREADY_WITH_EXPORT: {
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:
break; // Can't happen, but silences warning
}
@ -254,6 +257,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"NATIVE_METHOD_OVERRIDE",
"GET_NODE_DEFAULT_WITHOUT_ONREADY",
"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.");

View File

@ -86,6 +86,7 @@ public:
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.
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,
};
@ -130,6 +131,7 @@ public:
ERROR, // NATIVE_METHOD_OVERRIDE // 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.
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.");

View File

@ -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 + "'");
}
// 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();
// Create object instance for test.
@ -577,14 +585,6 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
obj->set_script(script);
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.
Callable::CallError call_err;
instance->callp(GDScriptTestRunner::test_function_name, nullptr, 0, call_err);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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")

View File

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

View File

@ -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)

View File

@ -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

View File

@ -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")

View File

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

View File

@ -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")

View File

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