-
-
Notifications
You must be signed in to change notification settings - Fork 21.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow setters and getters to take additional arguments #78991
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -864,6 +864,31 @@ void GDScript::unload_static() const { | |||||||||||||
GDScriptCache::remove_script(fully_qualified_name); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
Callable::CallError GDScript::call_getter(const MemberInfo &p_member, const StringName &p_name, Variant &r_ret) const { | ||||||||||||||
Callable::CallError ce; | ||||||||||||||
if (p_member.getter_with_name) { | ||||||||||||||
Variant property_name = p_name; | ||||||||||||||
const Variant *args = &property_name; | ||||||||||||||
r_ret = const_cast<GDScript *>(this)->callp(p_member.getter, &args, 1, ce); | ||||||||||||||
} else { | ||||||||||||||
r_ret = const_cast<GDScript *>(this)->callp(p_member.getter, nullptr, 0, ce); | ||||||||||||||
} | ||||||||||||||
return ce; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
Callable::CallError GDScript::call_setter(const MemberInfo &p_member, const StringName &p_name, const Variant &p_value) { | ||||||||||||||
Callable::CallError ce; | ||||||||||||||
if (p_member.setter_with_name) { | ||||||||||||||
Variant property_name = p_name; | ||||||||||||||
const Variant *args[2] = { &property_name, &p_value }; | ||||||||||||||
callp(p_member.setter, args, 2, ce); | ||||||||||||||
} else { | ||||||||||||||
const Variant *args = &p_value; | ||||||||||||||
callp(p_member.setter, &args, 1, ce); | ||||||||||||||
} | ||||||||||||||
return ce; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
Variant GDScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { | ||||||||||||||
GDScript *top = this; | ||||||||||||||
while (top) { | ||||||||||||||
|
@@ -896,13 +921,12 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const { | |||||||||||||
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); | ||||||||||||||
const GDScript::MemberInfo *static_member = &E->value; | ||||||||||||||
call_getter(*static_member, p_name, r_ret); // TODO: returned call error not used. | ||||||||||||||
return true; | ||||||||||||||
} | ||||||||||||||
r_ret = top->static_variables[E->value.index]; | ||||||||||||||
|
@@ -942,32 +966,30 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) { | |||||||||||||
reload(); | ||||||||||||||
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; | ||||||||||||||
const MemberInfo *static_member = &E->value; | ||||||||||||||
Variant value = p_value; | ||||||||||||||
if (member->data_type.has_type && !member->data_type.is_type(value)) { | ||||||||||||||
if (static_member->data_type.has_type && !static_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)) { | ||||||||||||||
Variant::construct(static_member->data_type.builtin_type, value, &args, 1, err); | ||||||||||||||
if (err.error != Callable::CallError::CALL_OK || !static_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; | ||||||||||||||
if (static_member->setter) { | ||||||||||||||
Callable::CallError ce = call_setter(*static_member, p_name, value); | ||||||||||||||
if (ce.error == Callable::CallError::CALL_OK) { | ||||||||||||||
return true; // TODO: should this just be return ce.error == Callable::CallError::CALL_OK? | ||||||||||||||
} | ||||||||||||||
} else { | ||||||||||||||
top->static_variables.write[member->index] = value; | ||||||||||||||
top->static_variables.write[static_member->index] = value; | ||||||||||||||
return true; | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
top = top->_base; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
|
@@ -1569,10 +1591,10 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { | |||||||||||||
} | ||||||||||||||
} | ||||||||||||||
if (member->setter) { | ||||||||||||||
const Variant *args = &value; | ||||||||||||||
Callable::CallError err; | ||||||||||||||
callp(member->setter, &args, 1, err); | ||||||||||||||
return err.error == Callable::CallError::CALL_OK; | ||||||||||||||
Callable::CallError ce = call_setter(*member, p_name, value); | ||||||||||||||
if (ce.error == Callable::CallError::CALL_OK) { | ||||||||||||||
return true; // TODO: should this just be return ce.error == Callable::CallError::CALL_OK? | ||||||||||||||
} | ||||||||||||||
Comment on lines
+1594
to
+1597
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
} else { | ||||||||||||||
members.write[member->index] = value; | ||||||||||||||
return true; | ||||||||||||||
|
@@ -1585,23 +1607,23 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { | |||||||||||||
{ | ||||||||||||||
HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = sptr->static_variables_indices.find(p_name); | ||||||||||||||
if (E) { | ||||||||||||||
const GDScript::MemberInfo *member = &E->value; | ||||||||||||||
const GDScript::MemberInfo *static_member = &E->value; | ||||||||||||||
Variant value = p_value; | ||||||||||||||
if (member->data_type.has_type && !member->data_type.is_type(value)) { | ||||||||||||||
if (static_member->data_type.has_type && !static_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)) { | ||||||||||||||
Variant::construct(static_member->data_type.builtin_type, value, &args, 1, err); | ||||||||||||||
if (err.error != Callable::CallError::CALL_OK || !static_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; | ||||||||||||||
if (static_member->setter) { | ||||||||||||||
Callable::CallError ce = call_setter(*static_member, p_name, value); | ||||||||||||||
if (ce.error == Callable::CallError::CALL_OK) { | ||||||||||||||
return true; // TODO: should this just be return ce.error == Callable::CallError::CALL_OK? | ||||||||||||||
Comment on lines
+1621
to
+1623
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
} | ||||||||||||||
} else { | ||||||||||||||
sptr->static_variables.write[member->index] = value; | ||||||||||||||
sptr->static_variables.write[static_member->index] = value; | ||||||||||||||
return true; | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
@@ -1631,14 +1653,14 @@ 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; | ||||||||||||||
const GDScript::MemberInfo *member = &E->value; | ||||||||||||||
if (member->getter) { | ||||||||||||||
Callable::CallError ce = call_getter(*member, p_name, r_ret); | ||||||||||||||
if (ce.error == Callable::CallError::CALL_OK) { | ||||||||||||||
return true; // TODO: should this just be return ce.error == Callable::CallError::CALL_OK? | ||||||||||||||
} | ||||||||||||||
Comment on lines
+1658
to
1661
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
} | ||||||||||||||
r_ret = members[E->value.index]; | ||||||||||||||
r_ret = members[member->index]; | ||||||||||||||
return true; | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
@@ -1656,12 +1678,14 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { | |||||||||||||
{ | ||||||||||||||
HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = sptr->static_variables_indices.find(p_name); | ||||||||||||||
if (E) { | ||||||||||||||
if (E->value.getter) { | ||||||||||||||
Callable::CallError ce; | ||||||||||||||
r_ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce); | ||||||||||||||
return true; | ||||||||||||||
const GDScript::MemberInfo *static_member = &E->value; | ||||||||||||||
if (static_member->getter) { | ||||||||||||||
Callable::CallError ce = sptr->call_getter(*static_member, p_name, r_ret); | ||||||||||||||
if (ce.error == Callable::CallError::CALL_OK) { | ||||||||||||||
return true; // TODO: should this just be return ce.error == Callable::CallError::CALL_OK? | ||||||||||||||
} | ||||||||||||||
Comment on lines
+1683
to
+1686
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
} | ||||||||||||||
r_ret = sptr->static_variables[E->value.index]; | ||||||||||||||
r_ret = sptr->static_variables[static_member->index]; | ||||||||||||||
return true; | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
@@ -1697,8 +1721,8 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { | |||||||||||||
{ | ||||||||||||||
HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get); | ||||||||||||||
if (E) { | ||||||||||||||
Variant name = p_name; | ||||||||||||||
const Variant *args[1] = { &name }; | ||||||||||||||
Variant property_name = p_name; | ||||||||||||||
const Variant *args[1] = { &property_name }; | ||||||||||||||
|
||||||||||||||
Callable::CallError err; | ||||||||||||||
Variant ret = const_cast<GDScriptFunction *>(E->value)->call(const_cast<GDScriptInstance *>(this), (const Variant **)args, 1, err); | ||||||||||||||
|
@@ -1828,8 +1852,8 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const | |||||||||||||
} | ||||||||||||||
|
||||||||||||||
bool GDScriptInstance::property_can_revert(const StringName &p_name) const { | ||||||||||||||
Variant name = p_name; | ||||||||||||||
const Variant *args[1] = { &name }; | ||||||||||||||
Variant property_name = p_name; | ||||||||||||||
const Variant *args[1] = { &property_name }; | ||||||||||||||
|
||||||||||||||
const GDScript *sptr = script.ptr(); | ||||||||||||||
while (sptr) { | ||||||||||||||
|
@@ -1848,8 +1872,8 @@ bool GDScriptInstance::property_can_revert(const StringName &p_name) const { | |||||||||||||
} | ||||||||||||||
|
||||||||||||||
bool GDScriptInstance::property_get_revert(const StringName &p_name, Variant &r_ret) const { | ||||||||||||||
Variant name = p_name; | ||||||||||||||
const Variant *args[1] = { &name }; | ||||||||||||||
Variant property_name = p_name; | ||||||||||||||
const Variant *args[1] = { &property_name }; | ||||||||||||||
|
||||||||||||||
const GDScript *sptr = script.ptr(); | ||||||||||||||
while (sptr) { | ||||||||||||||
|
@@ -1896,6 +1920,31 @@ bool GDScriptInstance::has_method(const StringName &p_method) const { | |||||||||||||
return false; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
Callable::CallError GDScriptInstance::call_getter(const GDScript::MemberInfo &p_member, const StringName &p_name, Variant &r_ret) const { | ||||||||||||||
Callable::CallError ce; | ||||||||||||||
if (p_member.getter_with_name) { | ||||||||||||||
Variant property_name = p_name; | ||||||||||||||
const Variant *args = &property_name; | ||||||||||||||
r_ret = const_cast<GDScriptInstance *>(this)->callp(p_member.getter, &args, 1, ce); | ||||||||||||||
} else { | ||||||||||||||
r_ret = const_cast<GDScriptInstance *>(this)->callp(p_member.getter, nullptr, 0, ce); | ||||||||||||||
} | ||||||||||||||
return ce; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
Callable::CallError GDScriptInstance::call_setter(const GDScript::MemberInfo &p_member, const StringName &p_name, const Variant &p_value) { | ||||||||||||||
Callable::CallError ce; | ||||||||||||||
if (p_member.setter_with_name) { | ||||||||||||||
Variant property_name = p_name; | ||||||||||||||
const Variant *args[2] = { &property_name, &p_value }; | ||||||||||||||
callp(p_member.setter, args, 2, ce); | ||||||||||||||
} else { | ||||||||||||||
const Variant *args = &p_value; | ||||||||||||||
callp(p_member.setter, &args, 1, ce); | ||||||||||||||
} | ||||||||||||||
return ce; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
Variant GDScriptInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { | ||||||||||||||
GDScript *sptr = script.ptr(); | ||||||||||||||
if (unlikely(p_method == SNAME("_ready"))) { | ||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -1335,15 +1335,23 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co | |||||
return_datatype.is_meta_type = false; | ||||||
} | ||||||
|
||||||
if (getter_function->parameters.size() != 0 || return_datatype.has_no_type()) { | ||||||
push_error(vformat(R"(Function "%s" cannot be used as getter because of its signature.)", getter_function->identifier->name), member.variable); | ||||||
if (getter_function->mandatory_parameter_count > 1) { | ||||||
push_error(R"(A getter cannot have more than 1 mandatory argument.)", member.variable); | ||||||
} else if (!is_type_compatible(member.variable->datatype, return_datatype, true)) { | ||||||
push_error(vformat(R"(Function with return type "%s" cannot be used as getter for a property of type "%s".)", return_datatype.to_string(), member.variable->datatype.to_string()), member.variable); | ||||||
|
||||||
push_error(vformat(R"(Getter return type "%s" is incompatible with variable type "%s".)", return_datatype.to_string(), member.variable->datatype.to_string()), member.variable); | ||||||
} else { | ||||||
has_valid_getter = true; | ||||||
if (getter_function->mandatory_parameter_count == 1) { | ||||||
GDScriptParser::DataType name_arg_type = getter_function->parameters[0]->get_datatype(); | ||||||
if (!name_arg_type.is_variant() && name_arg_type.is_hard_type()) { | ||||||
if (name_arg_type.kind != GDScriptParser::DataType::BUILTIN || (name_arg_type.builtin_type != Variant::STRING && name_arg_type.builtin_type != Variant::STRING_NAME)) { | ||||||
push_error(R"(First mandatory argument of a getter must be a property name of type String or StringName.)", member.variable); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
has_valid_getter = false; | ||||||
} | ||||||
} | ||||||
} | ||||||
#ifdef DEBUG_ENABLED | ||||||
if (member.variable->datatype.builtin_type == Variant::INT && return_datatype.builtin_type == Variant::FLOAT) { | ||||||
if (has_valid_getter && member.variable->datatype.builtin_type == Variant::INT && return_datatype.builtin_type == Variant::FLOAT) { | ||||||
parser->push_warning(member.variable, GDScriptWarning::NARROWING_CONVERSION); | ||||||
} | ||||||
#endif | ||||||
|
@@ -1358,27 +1366,38 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co | |||||
|
||||||
if (setter_function == nullptr) { | ||||||
push_error(vformat(R"(Setter "%s" not found.)", member.variable->setter_pointer->name), member.variable); | ||||||
|
||||||
} else if (setter_function->parameters.size() != 1) { | ||||||
push_error(vformat(R"(Function "%s" cannot be used as setter because of its signature.)", setter_function->identifier->name), member.variable); | ||||||
|
||||||
} else if (!is_type_compatible(member.variable->datatype, setter_function->parameters[0]->datatype, true)) { | ||||||
push_error(vformat(R"(Function with argument type "%s" cannot be used as setter for a property of type "%s".)", setter_function->parameters[0]->datatype.to_string(), member.variable->datatype.to_string()), member.variable); | ||||||
|
||||||
} else { | ||||||
has_valid_setter = true; | ||||||
|
||||||
if (setter_function->mandatory_parameter_count == 0 || setter_function->mandatory_parameter_count > 2) { | ||||||
push_error(vformat(R"(Setters must have 1 or 2 mandatory arguments.)"), member.variable); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
For consistency with the getter error message. |
||||||
} else { | ||||||
GDScriptParser::ParameterNode *value_arg = setter_function->parameters[setter_function->mandatory_parameter_count - 1]; | ||||||
if (!is_type_compatible(member.variable->datatype, value_arg->datatype, true)) { | ||||||
push_error(vformat(R"(Setter argument "%s" of type "%s" is incompatible with variable type "%s".)", value_arg->identifier->name, value_arg->get_datatype().to_string(), member.variable->datatype.to_string()), member.variable); | ||||||
} else { | ||||||
has_valid_setter = true; | ||||||
if (setter_function->mandatory_parameter_count == 2) { | ||||||
GDScriptParser::DataType name_arg_type = setter_function->parameters[0]->datatype; | ||||||
if (!name_arg_type.is_variant() && name_arg_type.is_hard_type()) { | ||||||
if (name_arg_type.kind != GDScriptParser::DataType::BUILTIN || (name_arg_type.builtin_type != Variant::STRING && name_arg_type.builtin_type != Variant::STRING_NAME)) { | ||||||
push_error(R"(First mandatory argument of a setter with 2 mandatory arguments must be a property name of type String or StringName.)", member.variable); | ||||||
has_valid_setter = false; | ||||||
} | ||||||
} | ||||||
} | ||||||
#ifdef DEBUG_ENABLED | ||||||
if (member.variable->datatype.builtin_type == Variant::FLOAT && setter_function->parameters[0]->datatype.builtin_type == Variant::INT) { | ||||||
parser->push_warning(member.variable, GDScriptWarning::NARROWING_CONVERSION); | ||||||
} | ||||||
if (has_valid_setter && member.variable->datatype.builtin_type == Variant::FLOAT && value_arg->datatype.builtin_type == Variant::INT) { | ||||||
parser->push_warning(member.variable, GDScriptWarning::NARROWING_CONVERSION); | ||||||
} | ||||||
#endif | ||||||
} | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
if (member.variable->datatype.is_variant() && has_valid_getter && has_valid_setter) { | ||||||
if (!is_type_compatible(getter_function->datatype, setter_function->parameters[0]->datatype, true)) { | ||||||
push_error(vformat(R"(Getter with type "%s" cannot be used along with setter of type "%s".)", getter_function->datatype.to_string(), setter_function->parameters[0]->datatype.to_string()), member.variable); | ||||||
GDScriptParser::DataType value_arg_type = setter_function->parameters[setter_function->mandatory_parameter_count - 1]->datatype; | ||||||
if (!is_type_compatible(getter_function->datatype, value_arg_type, true)) { | ||||||
push_error(vformat(R"(Getter with type "%s" cannot be used along with setter of type "%s".)", getter_function->datatype.to_string(), value_arg_type.to_string()), member.variable); | ||||||
} | ||||||
} | ||||||
} | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.