GDScript: Fix some bugs with static variables and functions

This commit is contained in:
Danil Alexeev 2023-05-16 13:03:53 +03:00
parent 598378513b
commit aebbbda080
No known key found for this signature in database
GPG Key ID: 124453E157DA8DC7
21 changed files with 624 additions and 194 deletions

View File

@ -878,44 +878,55 @@ Variant GDScript::callp(const StringName &p_method, const Variant **p_args, int
}
bool GDScript::_get(const StringName &p_name, Variant &r_ret) const {
{
const GDScript *top = this;
while (top) {
{
HashMap<StringName, Variant>::ConstIterator E = top->constants.find(p_name);
if (E) {
r_ret = E->value;
return true;
}
}
if (p_name == GDScriptLanguage::get_singleton()->strings._script_source) {
r_ret = get_source_code();
return true;
}
{
HashMap<StringName, Ref<GDScript>>::ConstIterator E = subclasses.find(p_name);
if (E) {
r_ret = E->value;
return true;
}
const GDScript *top = this;
while (top) {
{
HashMap<StringName, Variant>::ConstIterator E = top->constants.find(p_name);
if (E) {
r_ret = E->value;
return true;
}
{
HashMap<StringName, 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;
}
if (p_name == GDScriptLanguage::get_singleton()->strings._script_source) {
r_ret = get_source_code();
return true;
{
HashMap<StringName, MemberInfo>::ConstIterator E = top->static_variables_indices.find(p_name);
if (E) {
if (E->value.getter) {
Callable::CallError ce;
r_ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce);
return true;
}
r_ret = top->static_variables[E->value.index];
return true;
}
}
{
HashMap<StringName, GDScriptFunction *>::ConstIterator E = top->member_functions.find(p_name);
if (E && E->value->is_static()) {
if (top->rpc_config.has(p_name)) {
r_ret = Callable(memnew(GDScriptRPCCallable(const_cast<GDScript *>(top), E->key)));
} else {
r_ret = Callable(const_cast<GDScript *>(top), E->key);
}
return true;
}
}
{
HashMap<StringName, Ref<GDScript>>::ConstIterator E = top->subclasses.find(p_name);
if (E) {
r_ret = E->value;
return true;
}
}
top = top->_base;
}
return false;
@ -925,40 +936,60 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) {
if (p_name == GDScriptLanguage::get_singleton()->strings._script_source) {
set_source_code(p_value);
reload();
} else {
const GDScript *top = this;
while (top) {
HashMap<StringName, MemberInfo>::ConstIterator E = static_variables_indices.find(p_name);
if (E) {
const GDScript::MemberInfo *member = &E->value;
Variant value = p_value;
if (member->data_type.has_type && !member->data_type.is_type(value)) {
const Variant *args = &p_value;
Callable::CallError err;
Variant::construct(member->data_type.builtin_type, value, &args, 1, err);
if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) {
return false;
}
}
if (member->setter) {
const Variant *args = &value;
Callable::CallError err;
callp(member->setter, &args, 1, err);
return err.error == Callable::CallError::CALL_OK;
} else {
static_variables.write[member->index] = value;
return true;
}
}
top = top->_base;
}
return true;
}
return true;
GDScript *top = this;
while (top) {
HashMap<StringName, MemberInfo>::ConstIterator E = top->static_variables_indices.find(p_name);
if (E) {
const MemberInfo *member = &E->value;
Variant value = p_value;
if (member->data_type.has_type && !member->data_type.is_type(value)) {
const Variant *args = &p_value;
Callable::CallError err;
Variant::construct(member->data_type.builtin_type, value, &args, 1, err);
if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) {
return false;
}
}
if (member->setter) {
const Variant *args = &value;
Callable::CallError err;
callp(member->setter, &args, 1, err);
return err.error == Callable::CallError::CALL_OK;
} else {
top->static_variables.write[member->index] = value;
return true;
}
}
top = top->_base;
}
return false;
}
void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const {
p_properties->push_back(PropertyInfo(Variant::STRING, "script/source", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
List<PropertyInfo> property_list;
const GDScript *top = this;
while (top) {
for (const KeyValue<StringName, MemberInfo> &E : top->static_variables_indices) {
PropertyInfo pi = PropertyInfo(E.value.data_type);
pi.name = E.key;
pi.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; // For the script (as a class) it is a non-static property.
property_list.push_back(pi);
}
top = top->_base;
}
for (const List<PropertyInfo>::Element *E = property_list.back(); E; E = E->prev()) {
p_properties->push_back(E->get());
}
}
void GDScript::_bind_methods() {
@ -1037,6 +1068,16 @@ StringName GDScript::debug_get_member_by_index(int p_idx) const {
return "<error>";
}
StringName GDScript::debug_get_static_var_by_index(int p_idx) const {
for (const KeyValue<StringName, MemberInfo> &E : static_variables_indices) {
if (E.value.index == p_idx) {
return E.key;
}
}
return "<error>";
}
Ref<GDScript> GDScript::get_base() const {
return base;
}
@ -1376,8 +1417,8 @@ void GDScript::clear(GDScript::ClearData *p_clear_data) {
}
clearing = true;
GDScript::ClearData data;
GDScript::ClearData *clear_data = p_clear_data;
ClearData data;
ClearData *clear_data = p_clear_data;
bool is_root = false;
// If `clear_data` is `nullptr`, it means that it's the root.
@ -1398,12 +1439,12 @@ void GDScript::clear(GDScript::ClearData *p_clear_data) {
}
member_functions.clear();
for (KeyValue<StringName, GDScript::MemberInfo> &E : member_indices) {
for (KeyValue<StringName, MemberInfo> &E : member_indices) {
clear_data->scripts.insert(E.value.data_type.script_type_ref);
E.value.data_type.script_type_ref = Ref<Script>();
}
for (KeyValue<StringName, GDScript::MemberInfo> &E : static_variables_indices) {
for (KeyValue<StringName, MemberInfo> &E : static_variables_indices) {
clear_data->scripts.insert(E.value.data_type.script_type_ref);
E.value.data_type.script_type_ref = Ref<Script>();
}
@ -1486,7 +1527,6 @@ GDScript::~GDScript() {
//////////////////////////////
bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
//member
{
HashMap<StringName, GDScript::MemberInfo>::Iterator E = script->member_indices.find(p_name);
if (E) {
@ -1514,17 +1554,45 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
GDScript *sptr = script.ptr();
while (sptr) {
HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._set);
if (E) {
Variant name = p_name;
const Variant *args[2] = { &name, &p_value };
Callable::CallError err;
Variant ret = E->value->call(this, (const Variant **)args, 2, err);
if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) {
return true;
{
HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = sptr->static_variables_indices.find(p_name);
if (E) {
const GDScript::MemberInfo *member = &E->value;
Variant value = p_value;
if (member->data_type.has_type && !member->data_type.is_type(value)) {
const Variant *args = &p_value;
Callable::CallError err;
Variant::construct(member->data_type.builtin_type, value, &args, 1, err);
if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) {
return false;
}
}
if (member->setter) {
const Variant *args = &value;
Callable::CallError err;
callp(member->setter, &args, 1, err);
return err.error == Callable::CallError::CALL_OK;
} else {
sptr->static_variables.write[member->index] = value;
return true;
}
}
}
{
HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._set);
if (E) {
Variant name = p_name;
const Variant *args[2] = { &name, &p_value };
Callable::CallError err;
Variant ret = E->value->call(this, (const Variant **)args, 2, err);
if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) {
return true;
}
}
}
sptr = sptr->_base;
}
@ -1532,62 +1600,69 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
}
bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
{
HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = script->member_indices.find(p_name);
if (E) {
if (E->value.getter) {
Callable::CallError err;
r_ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err);
if (err.error == Callable::CallError::CALL_OK) {
return true;
}
}
r_ret = members[E->value.index];
return true;
}
}
const GDScript *sptr = script.ptr();
while (sptr) {
{
HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = script->member_indices.find(p_name);
HashMap<StringName, Variant>::ConstIterator E = sptr->constants.find(p_name);
if (E) {
r_ret = E->value;
return true;
}
}
{
HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = sptr->static_variables_indices.find(p_name);
if (E) {
if (E->value.getter) {
Callable::CallError err;
r_ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err);
if (err.error == Callable::CallError::CALL_OK) {
return true;
}
Callable::CallError ce;
r_ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce);
return true;
}
r_ret = members[E->value.index];
return true; //index found
r_ret = sptr->static_variables[E->value.index];
return true;
}
}
{
const GDScript *sl = sptr;
while (sl) {
HashMap<StringName, Variant>::ConstIterator E = sl->constants.find(p_name);
if (E) {
r_ret = E->value;
return true; //index found
}
sl = sl->_base;
HashMap<StringName, Vector<StringName>>::ConstIterator E = sptr->_signals.find(p_name);
if (E) {
r_ret = Signal(this->owner, E->key);
return true;
}
}
{
// Signals.
const GDScript *sl = sptr;
while (sl) {
HashMap<StringName, Vector<StringName>>::ConstIterator E = sl->_signals.find(p_name);
if (E) {
r_ret = Signal(this->owner, E->key);
return true; //index found
HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(p_name);
if (E) {
if (sptr->rpc_config.has(p_name)) {
r_ret = Callable(memnew(GDScriptRPCCallable(this->owner, E->key)));
} else {
r_ret = Callable(this->owner, E->key);
}
sl = sl->_base;
return true;
}
}
{
// Methods.
const GDScript *sl = sptr;
while (sl) {
HashMap<StringName, GDScriptFunction *>::ConstIterator E = sl->member_functions.find(p_name);
if (E) {
if (sptr->rpc_config.has(p_name)) {
r_ret = Callable(memnew(GDScriptRPCCallable(this->owner, E->key)));
} else {
r_ret = Callable(this->owner, E->key);
}
return true; //index found
}
sl = sl->_base;
HashMap<StringName, Ref<GDScript>>::ConstIterator E = sptr->subclasses.find(p_name);
if (E) {
r_ret = E->value;
return true;
}
}

View File

@ -227,6 +227,7 @@ public:
const HashMap<StringName, MemberInfo> &debug_get_member_indices() const { return member_indices; }
const HashMap<StringName, GDScriptFunction *> &debug_get_member_functions() const; //this is debug only
StringName debug_get_member_by_index(int p_idx) const;
StringName debug_get_static_var_by_index(int p_idx) const;
Variant _new(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
virtual bool can_instantiate() const override;

View File

@ -2469,9 +2469,15 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype();
if (assignee_type.is_constant || (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->is_constant)) {
if (assignee_type.is_constant) {
push_error("Cannot assign a new value to a constant.", p_assignment->assignee);
return;
} else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->is_constant) {
const GDScriptParser::DataType &base_type = static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->datatype;
if (base_type.kind != GDScriptParser::DataType::SCRIPT && base_type.kind != GDScriptParser::DataType::CLASS) { // Static variables.
push_error("Cannot assign a new value to a constant.", p_assignment->assignee);
return;
}
} else if (assignee_type.is_read_only) {
push_error("Cannot assign a new value to a read-only property.", p_assignment->assignee);
return;
@ -3516,7 +3522,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
} break;
case GDScriptParser::ClassNode::Member::FUNCTION: {
if (is_base && !base.is_meta_type) {
if (is_base && (!base.is_meta_type || member.function->is_static)) {
p_identifier->set_datatype(make_callable_type(member.function->info));
return;
}

View File

@ -853,6 +853,20 @@ void GDScriptByteCodeGenerator::write_get_member(const Address &p_target, const
append(p_name);
}
void GDScriptByteCodeGenerator::write_set_static_variable(const Address &p_value, const Address &p_class, int p_index) {
append_opcode(GDScriptFunction::OPCODE_SET_STATIC_VARIABLE);
append(p_value);
append(p_class);
append(p_index);
}
void GDScriptByteCodeGenerator::write_get_static_variable(const Address &p_target, const Address &p_class, int p_index) {
append_opcode(GDScriptFunction::OPCODE_GET_STATIC_VARIABLE);
append(p_target);
append(p_class);
append(p_index);
}
void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_target, const Address &p_source) {
switch (p_target.type.kind) {
case GDScriptDataType::BUILTIN: {

View File

@ -365,8 +365,6 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
return p_address.address | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS);
case Address::CONSTANT:
return p_address.address | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS);
case Address::STATIC_VARIABLE:
return p_address.address | (GDScriptFunction::ADDR_TYPE_STATIC_VAR << GDScriptFunction::ADDR_BITS);
case Address::LOCAL_VARIABLE:
case Address::FUNCTION_PARAMETER:
return p_address.address | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
@ -502,6 +500,8 @@ public:
virtual void write_get_named(const Address &p_target, const StringName &p_name, const Address &p_source) override;
virtual void write_set_member(const Address &p_value, const StringName &p_name) override;
virtual void write_get_member(const Address &p_target, const StringName &p_name) override;
virtual void write_set_static_variable(const Address &p_value, const Address &p_class, int p_index) override;
virtual void write_get_static_variable(const Address &p_target, const Address &p_class, int p_index) override;
virtual void write_assign(const Address &p_target, const Address &p_source) override;
virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) override;
virtual void write_assign_true(const Address &p_target) override;

View File

@ -45,7 +45,6 @@ public:
CLASS,
MEMBER,
CONSTANT,
STATIC_VARIABLE,
LOCAL_VARIABLE,
FUNCTION_PARAMETER,
TEMPORARY,
@ -111,6 +110,8 @@ public:
virtual void write_get_named(const Address &p_target, const StringName &p_name, const Address &p_source) = 0;
virtual void write_set_member(const Address &p_value, const StringName &p_name) = 0;
virtual void write_get_member(const Address &p_target, const StringName &p_name) = 0;
virtual void write_set_static_variable(const Address &p_value, const Address &p_class, int p_index) = 0;
virtual void write_get_static_variable(const Address &p_target, const Address &p_class, int p_index) = 0;
virtual void write_assign(const Address &p_target, const Address &p_source) = 0;
virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) = 0;
virtual void write_assign_true(const Address &p_target) = 0;

View File

@ -262,18 +262,27 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
}
// Try static variables.
if (codegen.script->static_variables_indices.has(identifier)) {
if (codegen.script->static_variables_indices[identifier].getter != StringName() && codegen.script->static_variables_indices[identifier].getter != codegen.function_name) {
// Perform getter.
GDScriptCodeGenerator::Address temp = codegen.add_temporary(codegen.script->static_variables_indices[identifier].data_type);
GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS);
Vector<GDScriptCodeGenerator::Address> args; // No argument needed.
gen->write_call(temp, class_addr, codegen.script->static_variables_indices[identifier].getter, args);
return temp;
} else {
// No getter or inside getter: direct variable access.
int idx = codegen.script->static_variables_indices[identifier].index;
return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::STATIC_VARIABLE, idx, codegen.script->static_variables_indices[identifier].data_type);
{
GDScript *scr = codegen.script;
while (scr) {
if (scr->static_variables_indices.has(identifier)) {
if (scr->static_variables_indices[identifier].getter != StringName() && scr->static_variables_indices[identifier].getter != codegen.function_name) {
// Perform getter.
GDScriptCodeGenerator::Address temp = codegen.add_temporary(scr->static_variables_indices[identifier].data_type);
GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS);
Vector<GDScriptCodeGenerator::Address> args; // No argument needed.
gen->write_call(temp, class_addr, scr->static_variables_indices[identifier].getter, args);
return temp;
} else {
// No getter or inside getter: direct variable access.
GDScriptCodeGenerator::Address temp = codegen.add_temporary(scr->static_variables_indices[identifier].data_type);
GDScriptCodeGenerator::Address _class = codegen.add_constant(scr);
int index = scr->static_variables_indices[identifier].index;
gen->write_get_static_variable(temp, _class, index);
return temp;
}
}
scr = scr->_base;
}
}
@ -926,6 +935,10 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
bool member_property_has_setter = false;
bool member_property_is_in_setter = false;
bool is_static = false;
GDScriptCodeGenerator::Address static_var_class;
int static_var_index = 0;
GDScriptDataType static_var_data_type;
StringName var_name;
StringName member_property_setter_function;
List<const GDScriptParser::SubscriptNode *> chain;
@ -939,19 +952,39 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// Check for a property.
if (n->base->type == GDScriptParser::Node::IDENTIFIER) {
GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(n->base);
StringName var_name = identifier->name;
var_name = identifier->name;
if (_is_class_member_property(codegen, var_name)) {
assign_class_member_property = var_name;
} else if (!_is_local_or_parameter(codegen, var_name) && (codegen.script->member_indices.has(var_name) || codegen.script->static_variables_indices.has(var_name))) {
is_member_property = true;
is_static = codegen.script->static_variables_indices.has(var_name);
const GDScript::MemberInfo &minfo = is_static ? codegen.script->static_variables_indices[var_name] : codegen.script->member_indices[var_name];
member_property_setter_function = minfo.setter;
member_property_has_setter = member_property_setter_function != StringName();
member_property_is_in_setter = member_property_has_setter && member_property_setter_function == codegen.function_name;
target_member_property.mode = is_static ? GDScriptCodeGenerator::Address::STATIC_VARIABLE : GDScriptCodeGenerator::Address::MEMBER;
target_member_property.address = minfo.index;
target_member_property.type = minfo.data_type;
} else if (!_is_local_or_parameter(codegen, var_name)) {
if (codegen.script->member_indices.has(var_name)) {
is_member_property = true;
is_static = false;
const GDScript::MemberInfo &minfo = codegen.script->member_indices[var_name];
member_property_setter_function = minfo.setter;
member_property_has_setter = member_property_setter_function != StringName();
member_property_is_in_setter = member_property_has_setter && member_property_setter_function == codegen.function_name;
target_member_property.mode = GDScriptCodeGenerator::Address::MEMBER;
target_member_property.address = minfo.index;
target_member_property.type = minfo.data_type;
} else {
// Try static variables.
GDScript *scr = codegen.script;
while (scr) {
if (scr->static_variables_indices.has(var_name)) {
is_member_property = true;
is_static = true;
const GDScript::MemberInfo &minfo = scr->static_variables_indices[var_name];
member_property_setter_function = minfo.setter;
member_property_has_setter = member_property_setter_function != StringName();
member_property_is_in_setter = member_property_has_setter && member_property_setter_function == codegen.function_name;
static_var_class = codegen.add_constant(scr);
static_var_index = minfo.index;
static_var_data_type = minfo.data_type;
break;
}
scr = scr->_base;
}
}
}
}
break;
@ -1104,8 +1137,13 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
if (member_property_has_setter && !member_property_is_in_setter) {
Vector<GDScriptCodeGenerator::Address> args;
args.push_back(assigned);
GDScriptCodeGenerator::Address self = is_static ? GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS) : GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF);
gen->write_call(GDScriptCodeGenerator::Address(), self, member_property_setter_function, args);
GDScriptCodeGenerator::Address call_base = is_static ? GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS) : GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF);
gen->write_call(GDScriptCodeGenerator::Address(), call_base, member_property_setter_function, args);
} else if (is_static) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(static_var_data_type);
gen->write_assign(temp, assigned);
gen->write_set_static_variable(temp, static_var_class, static_var_index);
gen->pop_temporary();
} else {
gen->write_assign(target_member_property, assigned);
}
@ -1155,18 +1193,42 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
bool has_setter = false;
bool is_in_setter = false;
bool is_static = false;
GDScriptCodeGenerator::Address static_var_class;
int static_var_index = 0;
GDScriptDataType static_var_data_type;
StringName var_name;
StringName setter_function;
StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name;
if (!_is_local_or_parameter(codegen, var_name) && (codegen.script->member_indices.has(var_name) || codegen.script->static_variables_indices.has(var_name))) {
is_member = true;
is_static = codegen.script->static_variables_indices.has(var_name);
GDScript::MemberInfo &minfo = is_static ? codegen.script->static_variables_indices[var_name] : codegen.script->member_indices[var_name];
setter_function = minfo.setter;
has_setter = setter_function != StringName();
is_in_setter = has_setter && setter_function == codegen.function_name;
member.mode = is_static ? GDScriptCodeGenerator::Address::STATIC_VARIABLE : GDScriptCodeGenerator::Address::MEMBER;
member.address = minfo.index;
member.type = minfo.data_type;
var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name;
if (!_is_local_or_parameter(codegen, var_name)) {
if (codegen.script->member_indices.has(var_name)) {
is_member = true;
is_static = false;
GDScript::MemberInfo &minfo = codegen.script->member_indices[var_name];
setter_function = minfo.setter;
has_setter = setter_function != StringName();
is_in_setter = has_setter && setter_function == codegen.function_name;
member.mode = GDScriptCodeGenerator::Address::MEMBER;
member.address = minfo.index;
member.type = minfo.data_type;
} else {
// Try static variables.
GDScript *scr = codegen.script;
while (scr) {
if (scr->static_variables_indices.has(var_name)) {
is_member = true;
is_static = true;
GDScript::MemberInfo &minfo = scr->static_variables_indices[var_name];
setter_function = minfo.setter;
has_setter = setter_function != StringName();
is_in_setter = has_setter && setter_function == codegen.function_name;
static_var_class = codegen.add_constant(scr);
static_var_index = minfo.index;
static_var_data_type = minfo.data_type;
break;
}
scr = scr->_base;
}
}
}
GDScriptCodeGenerator::Address target;
@ -1200,13 +1262,21 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
to_assign = assigned_value;
}
GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype(), codegen.script);
if (has_setter && !is_in_setter) {
// Call setter.
Vector<GDScriptCodeGenerator::Address> args;
args.push_back(to_assign);
gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), setter_function, args);
GDScriptCodeGenerator::Address call_base = is_static ? GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS) : GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF);
gen->write_call(GDScriptCodeGenerator::Address(), call_base, setter_function, args);
} else if (is_static) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(static_var_data_type);
if (assignment->use_conversion_assign) {
gen->write_assign_with_conversion(temp, to_assign);
} else {
gen->write_assign(temp, to_assign);
}
gen->write_set_static_variable(temp, static_var_class, static_var_index);
gen->pop_temporary();
} else {
// Just assign.
if (assignment->use_conversion_assign) {
@ -2062,11 +2132,11 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
}
GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
if (field_type.has_type) {
codegen.generator->write_newline(field->start_line);
GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
if (field_type.has_container_element_type()) {
codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>());
} else if (field_type.kind == GDScriptDataType::BUILTIN) {
@ -2093,9 +2163,6 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
continue;
}
GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
if (field->initializer) {
// Emit proper line change.
codegen.generator->write_newline(field->initializer->start_line);
@ -2106,6 +2173,9 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
return nullptr;
}
GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
if (field->use_conversion_assign) {
codegen.generator->write_assign_with_conversion(dst_address, src_address);
} else {
@ -2235,6 +2305,8 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
codegen.is_static = is_static;
codegen.generator->write_start(p_script, func_name, is_static, rpc_config, return_type);
// The static initializer is always called on the same class where the static variables are defined,
// so the CLASS address (current class) can be used instead of `codegen.add_constant(p_script)`.
GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS);
// Initialize the default values for typed variables before anything.
@ -2251,20 +2323,18 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
}
GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
if (field_type.has_type) {
codegen.generator->write_newline(field->start_line);
if (field_type.has_container_element_type()) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
codegen.generator->write_construct_typed_array(temp, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>());
codegen.generator->write_set_named(class_addr, field->identifier->name, temp);
codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index);
codegen.generator->pop_temporary();
} else if (field_type.kind == GDScriptDataType::BUILTIN) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
codegen.generator->write_construct(temp, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
codegen.generator->write_set_named(class_addr, field->identifier->name, temp);
codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index);
codegen.generator->pop_temporary();
}
// The `else` branch is for objects, in such case we leave it as `null`.
@ -2281,8 +2351,6 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
continue;
}
GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
if (field->initializer) {
// Emit proper line change.
codegen.generator->write_newline(field->initializer->start_line);
@ -2293,7 +2361,9 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
return nullptr;
}
GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
if (field->use_conversion_assign) {
codegen.generator->write_assign_with_conversion(temp, src_address);
} else {
@ -2303,7 +2373,7 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
codegen.generator->pop_temporary();
}
codegen.generator->write_set_named(class_addr, field->identifier->name, temp);
codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index);
codegen.generator->pop_temporary();
}
}

View File

@ -312,6 +312,36 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 3;
} break;
case OPCODE_SET_STATIC_VARIABLE: {
text += "set_static_variable script(";
Ref<GDScript> gdscript = get_constant(_code_ptr[ip + 2] & GDScriptFunction::ADDR_MASK);
text += gdscript.is_valid() ? gdscript->get_fully_qualified_name().get_file() : "<unknown script>";
text += ")";
if (gdscript.is_valid()) {
text += "[\"" + gdscript->debug_get_static_var_by_index(_code_ptr[ip + 3]) + "\"]";
} else {
text += "[<index " + itos(_code_ptr[ip + 3]) + ">]";
}
text += " = ";
text += DADDR(1);
incr += 4;
} break;
case OPCODE_GET_STATIC_VARIABLE: {
text += "get_static_variable ";
text += DADDR(1);
text += " = script(";
Ref<GDScript> gdscript = get_constant(_code_ptr[ip + 2] & GDScriptFunction::ADDR_MASK);
text += gdscript.is_valid() ? gdscript->get_fully_qualified_name().get_file() : "<unknown script>";
text += ")";
if (gdscript.is_valid()) {
text += "[\"" + gdscript->debug_get_static_var_by_index(_code_ptr[ip + 3]) + "\"]";
} else {
text += "[<index " + itos(_code_ptr[ip + 3]) + ">]";
}
incr += 4;
} break;
case OPCODE_ASSIGN: {
text += "assign ";
text += DADDR(1);

View File

@ -149,6 +149,7 @@ public:
operator PropertyInfo() const {
PropertyInfo info;
info.usage = PROPERTY_USAGE_NONE;
if (has_type) {
switch (kind) {
case UNINITIALIZED:
@ -238,6 +239,8 @@ public:
OPCODE_GET_NAMED_VALIDATED,
OPCODE_SET_MEMBER,
OPCODE_GET_MEMBER,
OPCODE_SET_STATIC_VARIABLE, // Only for GDScript.
OPCODE_GET_STATIC_VARIABLE, // Only for GDScript.
OPCODE_ASSIGN,
OPCODE_ASSIGN_TRUE,
OPCODE_ASSIGN_FALSE,
@ -410,14 +413,14 @@ public:
ADDR_TYPE_STACK = 0,
ADDR_TYPE_CONSTANT = 1,
ADDR_TYPE_MEMBER = 2,
ADDR_TYPE_STATIC_VAR = 3,
ADDR_TYPE_MAX = 4,
ADDR_TYPE_MAX = 3,
};
enum FixedAddresses {
ADDR_STACK_SELF = 0,
ADDR_STACK_CLASS = 1,
ADDR_STACK_NIL = 2,
FIXED_ADDRESSES_MAX = 3,
ADDR_SELF = ADDR_STACK_SELF | (ADDR_TYPE_STACK << ADDR_BITS),
ADDR_CLASS = ADDR_STACK_CLASS | (ADDR_TYPE_STACK << ADDR_BITS),
ADDR_NIL = ADDR_STACK_NIL | (ADDR_TYPE_STACK << ADDR_BITS),

View File

@ -3827,8 +3827,12 @@ bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node
}
VariableNode *variable = static_cast<VariableNode *>(p_node);
if (variable->is_static) {
push_error(R"("@onready" annotation cannot be applied to a static variable.)", p_annotation);
return false;
}
if (variable->onready) {
push_error(R"("@onready" annotation can only be used once per variable.)");
push_error(R"("@onready" annotation can only be used once per variable.)", p_annotation);
return false;
}
variable->onready = true;
@ -3841,6 +3845,10 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
VariableNode *variable = static_cast<VariableNode *>(p_node);
if (variable->is_static) {
push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation);
return false;
}
if (variable->exported) {
push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation);
return false;

View File

@ -217,6 +217,8 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_GET_NAMED_VALIDATED, \
&&OPCODE_SET_MEMBER, \
&&OPCODE_GET_MEMBER, \
&&OPCODE_SET_STATIC_VARIABLE, \
&&OPCODE_GET_STATIC_VARIABLE, \
&&OPCODE_ASSIGN, \
&&OPCODE_ASSIGN_TRUE, \
&&OPCODE_ASSIGN_FALSE, \
@ -666,7 +668,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
Variant *m_v = instruction_args[m_idx]
#ifdef DEBUG_ENABLED
uint64_t function_start_time = 0;
uint64_t function_call_time = 0;
@ -679,11 +680,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
bool exit_ok = false;
bool awaited = false;
#endif
#ifdef DEBUG_ENABLED
int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0, script->static_variables.size() };
int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0 };
#endif
Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr, script->static_variables.ptrw() };
Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr };
#ifdef DEBUG_ENABLED
OPCODE_WHILE(ip < _code_size) {
@ -1171,6 +1173,42 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
OPCODE(OPCODE_SET_STATIC_VARIABLE) {
CHECK_SPACE(4);
GET_VARIANT_PTR(value, 0);
GET_VARIANT_PTR(_class, 1);
GDScript *gdscript = Object::cast_to<GDScript>(_class->operator Object *());
GD_ERR_BREAK(!gdscript);
int index = _code_ptr[ip + 3];
GD_ERR_BREAK(index < 0 || index >= gdscript->static_variables.size());
gdscript->static_variables.write[index] = *value;
ip += 4;
}
DISPATCH_OPCODE;
OPCODE(OPCODE_GET_STATIC_VARIABLE) {
CHECK_SPACE(4);
GET_VARIANT_PTR(target, 0);
GET_VARIANT_PTR(_class, 1);
GDScript *gdscript = Object::cast_to<GDScript>(_class->operator Object *());
GD_ERR_BREAK(!gdscript);
int index = _code_ptr[ip + 3];
GD_ERR_BREAK(index < 0 || index >= gdscript->static_variables.size());
*target = gdscript->static_variables[index];
ip += 4;
}
DISPATCH_OPCODE;
OPCODE(OPCODE_ASSIGN) {
CHECK_SPACE(3);
GET_VARIANT_PTR(dst, 0);
@ -3620,7 +3658,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#endif
// Free stack, except reserved addresses.
for (int i = 3; i < _stack_size; i++) {
for (int i = FIXED_ADDRESSES_MAX; i < _stack_size; i++) {
stack[i].~Variant();
}
#ifdef DEBUG_ENABLED
@ -3628,7 +3666,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#endif
// Always free reserved addresses, since they are never copied.
for (int i = 0; i < 3; i++) {
for (int i = 0; i < FIXED_ADDRESSES_MAX; i++) {
stack[i].~Variant();
}

View File

@ -0,0 +1,8 @@
# GH-77098 p.3
@static_unload
@export static var a: int
func test():
pass

View File

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Annotation "@export" cannot be applied to a static variable.

View File

@ -0,0 +1,58 @@
# GH-77098 p.4
@static_unload
class A:
class InnerClass:
pass
enum NamedEnum { VALUE = 111 }
enum { UNNAMED_ENUM_VALUE = 222 }
const CONSTANT = 333
static var static_var := 1
static func static_func() -> int:
return 444
class B extends A:
func test_self():
print(self.InnerClass is GDScript)
print(self.NamedEnum)
print(self.NamedEnum.VALUE)
print(self.UNNAMED_ENUM_VALUE)
print(self.CONSTANT)
@warning_ignore("static_called_on_instance")
print(self.static_func())
prints("test_self before:", self.static_var)
self.static_var = 2
prints("test_self after:", self.static_var)
func test():
var hard := B.new()
hard.test_self()
print(hard.InnerClass is GDScript)
print(hard.NamedEnum)
print(hard.NamedEnum.VALUE)
print(hard.UNNAMED_ENUM_VALUE)
print(hard.CONSTANT)
@warning_ignore("static_called_on_instance")
print(hard.static_func())
prints("hard before:", hard.static_var)
hard.static_var = 3
prints("hard after:", hard.static_var)
var weak: Variant = B.new()
print(weak.InnerClass is GDScript)
print(weak.NamedEnum)
print(weak.NamedEnum.VALUE)
print(weak.UNNAMED_ENUM_VALUE)
print(weak.CONSTANT)
@warning_ignore("unsafe_method_access")
print(weak.static_func())
prints("weak before:", weak.static_var)
weak.static_var = 4
prints("weak after:", weak.static_var)

View File

@ -0,0 +1,25 @@
GDTEST_OK
true
{ "VALUE": 111 }
111
222
333
444
test_self before: 1
test_self after: 2
true
{ "VALUE": 111 }
111
222
333
444
hard before: 2
hard after: 3
true
{ "VALUE": 111 }
111
222
333
444
weak before: 3
weak after: 4

View File

@ -0,0 +1,17 @@
# GH-41919
class_name TestStaticFuncAsCallable
class InnerClass:
static func inner_my_func():
print("inner_my_func")
static func my_func():
print("my_func")
var a: Callable = TestStaticFuncAsCallable.my_func
var b: Callable = InnerClass.inner_my_func
func test():
a.call()
b.call()

View File

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

View File

@ -33,24 +33,24 @@ func test():
prints("perm:", perm)
prints("prop:", prop)
print("other.perm:", StaticVariablesOther.perm)
print("other.prop:", StaticVariablesOther.prop)
prints("other.perm:", StaticVariablesOther.perm)
prints("other.prop:", StaticVariablesOther.prop)
StaticVariablesOther.perm = 2
StaticVariablesOther.prop = "foo"
print("other.perm:", StaticVariablesOther.perm)
print("other.prop:", StaticVariablesOther.prop)
prints("other.perm:", StaticVariablesOther.perm)
prints("other.prop:", StaticVariablesOther.prop)
@warning_ignore("unsafe_method_access")
var path = get_script().get_path().get_base_dir()
var other = load(path + "/static_variables_load.gd")
var other = load(path + "/static_variables_load.gd")
print("load.perm:", other.perm)
print("load.prop:", other.prop)
prints("load.perm:", other.perm)
prints("load.prop:", other.prop)
other.perm = 3
other.prop = "bar"
print("load.perm:", other.perm)
print("load.prop:", other.prop)
prints("load.perm:", other.perm)
prints("load.prop:", other.prop)

View File

@ -3,14 +3,14 @@ Inner._static_init inner
InnerInner._static_init inner inner
data: data
perm: 0
prop: prefix Hello! suffix
prop: Hello! suffix
perm: 1
prop: prefix World! suffix
other.perm:0
other.prop:prefix Hello! suffix
other.perm:2
other.prop:prefix foo suffix
load.perm:0
load.prop:prefix Hello! suffix
load.perm:3
load.prop:prefix bar suffix
other.perm: 0
other.prop: Hello! suffix
other.perm: 2
other.prop: prefix foo suffix
load.perm: 0
load.prop: Hello! suffix
load.perm: 3
load.prop: prefix bar suffix

View File

@ -0,0 +1,56 @@
@static_unload
class A:
static var x: int = 1
static var y: int = 42:
set(_value):
print("The setter is NOT called on initialization.") # GH-77098 p.1
static func _static_init() -> void:
prints("A _static_init begin:", x)
x = -1
prints("A _static_init end:", x)
static func sf(p_x: int) -> void:
x = p_x
prints("sf:", x)
# GH-77331
func f(p_x: int) -> void:
x = p_x
prints("f:", x)
class B extends A:
static func _static_init() -> void:
prints("B _static_init begin:", x)
x = -2
prints("B _static_init end:", x)
static func sg(p_x: int) -> void:
x = p_x
prints("sg:", x)
func g(p_x: int) -> void:
x = p_x
prints("g:", x)
func h(p_x: int) -> void:
print("h: call f(%d)" % p_x)
f(p_x)
func test():
prints(A.x, B.x)
A.x = 1 # GH-77098 p.2
prints(A.x, B.x)
B.x = 2
prints(A.x, B.x)
A.sf(3)
B.sf(4)
B.sg(5)
var b := B.new()
b.f(6)
b.g(7)
b.h(8)

View File

@ -0,0 +1,15 @@
GDTEST_OK
A _static_init begin: 1
A _static_init end: -1
B _static_init begin: -1
B _static_init end: -2
-2 -2
1 1
2 2
sf: 3
sf: 4
sg: 5
f: 6
g: 7
h: call f(8)
f: 8