diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index ccbcb3ee96ee..a679f2423170 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -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(this)->callp(p_member.getter, &args, 1, ce); + } else { + r_ret = const_cast(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::ConstIterator E = top->static_variables_indices.find(p_name); if (E) { if (E->value.getter) { - Callable::CallError ce; - r_ret = const_cast(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::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? + } } else { members.write[member->index] = value; return true; @@ -1585,23 +1607,23 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { { HashMap::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? + } } 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::ConstIterator E = script->member_indices.find(p_name); if (E) { - if (E->value.getter) { - Callable::CallError err; - r_ret = const_cast(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? } } - 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::ConstIterator E = sptr->static_variables_indices.find(p_name); if (E) { - if (E->value.getter) { - Callable::CallError ce; - r_ret = const_cast(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? + } } - 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::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(E->value)->call(const_cast(this), (const Variant **)args, 1, err); @@ -1828,8 +1852,8 @@ void GDScriptInstance::get_property_list(List *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(this)->callp(p_member.getter, &args, 1, ce); + } else { + r_ret = const_cast(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"))) { diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 0ba007683c9c..8f8c12fc639a 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -67,7 +67,9 @@ class GDScript : public Script { struct MemberInfo { int index = 0; StringName setter; + bool setter_with_name = false; StringName getter; + bool getter_with_name = false; GDScriptDataType data_type; }; @@ -180,6 +182,9 @@ class GDScript : public Script { GDScript *_get_gdscript_from_variant(const Variant &p_variant); void _get_dependencies(RBSet &p_dependencies, const GDScript *p_except); + Callable::CallError call_getter(const MemberInfo &p_member_info, const StringName &p_name, Variant &r_ret) const; + Callable::CallError call_setter(const MemberInfo &p_member_info, const StringName &p_name, const Variant &p_value); + protected: bool _get(const StringName &p_name, Variant &r_ret) const; bool _set(const StringName &p_name, const Variant &p_value); @@ -314,6 +319,9 @@ class GDScriptInstance : public ScriptInstance { SelfList::List pending_func_states; + Callable::CallError call_getter(const GDScript::MemberInfo &p_member_info, const StringName &p_name, Variant &r_ret) const; + Callable::CallError call_setter(const GDScript::MemberInfo &p_member_info, const StringName &p_name, const Variant &p_value); + public: virtual Object *get_owner() { return owner; } diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 18c69467dc19..a0405d839df3 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -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); + 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); + } 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); } } } diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 985eb97b29e2..51b8ac682eda 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -263,7 +263,10 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code if (codegen.script->member_indices[identifier].getter != StringName() && codegen.script->member_indices[identifier].getter != codegen.function_name) { // Perform getter. GDScriptCodeGenerator::Address temp = codegen.add_temporary(codegen.script->member_indices[identifier].data_type); - Vector args; // No argument needed. + Vector args; + if (codegen.script->member_indices[identifier].getter_with_name) { + args.push_back(codegen.add_constant(identifier)); + } gen->write_call_self(temp, codegen.script->member_indices[identifier].getter, args); return temp; } else { @@ -352,7 +355,10 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Perform getter. GDScriptCodeGenerator::Address temp = codegen.add_temporary(scr->static_variables_indices[identifier].data_type); GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS); - Vector args; // No argument needed. + Vector args; + if (codegen.script->static_variables_indices[identifier].getter_with_name) { + args.push_back(codegen.add_constant(identifier)); + } gen->write_call(temp, class_addr, scr->static_variables_indices[identifier].getter, args); return temp; } else { @@ -959,6 +965,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code GDScriptDataType static_var_data_type; StringName var_name; StringName member_property_setter_function; + bool member_property_setter_with_name = false; List chain; @@ -980,6 +987,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code is_static = false; const GDScript::MemberInfo &minfo = codegen.script->member_indices[var_name]; member_property_setter_function = minfo.setter; + member_property_setter_with_name = minfo.setter_with_name; 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; @@ -994,6 +1002,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code is_static = true; const GDScript::MemberInfo &minfo = scr->static_variables_indices[var_name]; member_property_setter_function = minfo.setter; + member_property_setter_with_name = minfo.setter_with_name; 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); @@ -1139,7 +1148,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code if (!known_type || !is_shared) { // If this is a class member property, also assign to it. - // This allow things like: position.x += 2.0 + // This allows things like: position.x += 2.0. if (assign_class_member_property != StringName()) { if (!known_type) { gen->write_jump_if_shared(assigned); @@ -1154,7 +1163,11 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code gen->write_jump_if_shared(assigned); } if (member_property_has_setter && !member_property_is_in_setter) { + // Call setter. Vector args; + if (member_property_setter_with_name) { + args.push_back(codegen.add_constant(var_name)); + } args.push_back(assigned); 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); @@ -1217,6 +1230,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code GDScriptDataType static_var_data_type; StringName var_name; StringName setter_function; + bool setter_with_name = false; var_name = static_cast(assignment->assignee)->name; if (!_is_local_or_parameter(codegen, var_name)) { if (codegen.script->member_indices.has(var_name)) { @@ -1224,6 +1238,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code is_static = false; GDScript::MemberInfo &minfo = codegen.script->member_indices[var_name]; setter_function = minfo.setter; + setter_with_name = minfo.setter_with_name; has_setter = setter_function != StringName(); is_in_setter = has_setter && setter_function == codegen.function_name; member.mode = GDScriptCodeGenerator::Address::MEMBER; @@ -1238,6 +1253,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code is_static = true; GDScript::MemberInfo &minfo = scr->static_variables_indices[var_name]; setter_function = minfo.setter; + setter_with_name = minfo.setter_with_name; has_setter = setter_function != StringName(); is_in_setter = has_setter && setter_function == codegen.function_name; static_var_class = codegen.add_constant(scr); @@ -1284,6 +1300,9 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code if (has_setter && !is_in_setter) { // Call setter. Vector args; + if (setter_with_name) { + args.push_back(codegen.add_constant(var_name)); + } args.push_back(to_assign); 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); @@ -2643,9 +2662,11 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri case GDScriptParser::VariableNode::PROP_SETGET: if (variable->setter_pointer != nullptr) { minfo.setter = variable->setter_pointer->name; + minfo.setter_with_name = p_class->get_member(variable->setter_pointer->name).function->mandatory_parameter_count == 2; } if (variable->getter_pointer != nullptr) { minfo.getter = variable->getter_pointer->name; + minfo.getter_with_name = p_class->get_member(variable->getter_pointer->name).function->mandatory_parameter_count == 1; } break; case GDScriptParser::VariableNode::PROP_INLINE: diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index a0d02b12b515..dd8b7981440a 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -1387,6 +1387,7 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod push_error("Cannot have mandatory parameters after optional parameters."); continue; } + p_function->mandatory_parameter_count++; } if (p_function->parameters_indices.has(parameter->identifier->name)) { push_error(vformat(R"(Parameter with name "%s" was already declared for this %s.)", parameter->identifier->name, p_type)); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 9690784cba25..fd2bd52002aa 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -829,6 +829,7 @@ class GDScriptParser { IdentifierNode *identifier = nullptr; Vector parameters; HashMap parameters_indices; + int mandatory_parameter_count = 0; TypeNode *return_type = nullptr; SuiteNode *body = nullptr; bool is_static = false; diff --git a/modules/gdscript/tests/scripts/analyzer/errors/getter_with_too_many_args.gd b/modules/gdscript/tests/scripts/analyzer/errors/getter_with_too_many_args.gd new file mode 100644 index 000000000000..b5d4e9ef4ba1 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/getter_with_too_many_args.gd @@ -0,0 +1,8 @@ +var a: int: get = get_named + +func test(): + pass + +func get_named(name: int, oops: int) -> int: + prints("get_named", name) + return a diff --git a/modules/gdscript/tests/scripts/analyzer/errors/getter_with_too_many_args.out b/modules/gdscript/tests/scripts/analyzer/errors/getter_with_too_many_args.out new file mode 100644 index 000000000000..0e382121fdaf --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/getter_with_too_many_args.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +A getter cannot have more than 1 mandatory argument. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/getter_with_wrong_name_arg.gd b/modules/gdscript/tests/scripts/analyzer/errors/getter_with_wrong_name_arg.gd new file mode 100644 index 000000000000..63e0e1574f0d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/getter_with_wrong_name_arg.gd @@ -0,0 +1,8 @@ +var a: int: get = get_named + +func test(): + pass + +func get_named(name: int) -> int: + prints("get_named", name) + return a diff --git a/modules/gdscript/tests/scripts/analyzer/errors/getter_with_wrong_name_arg.out b/modules/gdscript/tests/scripts/analyzer/errors/getter_with_wrong_name_arg.out new file mode 100644 index 000000000000..fa9b7dd12dad --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/getter_with_wrong_name_arg.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +First mandatory argument of a getter must be a property name of type String or StringName. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/getter_with_wrong_return_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/getter_with_wrong_return_type.gd new file mode 100644 index 000000000000..ea8ea6d96ad3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/getter_with_wrong_return_type.gd @@ -0,0 +1,8 @@ +var a: int: get = getter + +func test(): + pass + +func getter() -> String: + prints("getter") + return a diff --git a/modules/gdscript/tests/scripts/analyzer/errors/getter_with_wrong_return_type.out b/modules/gdscript/tests/scripts/analyzer/errors/getter_with_wrong_return_type.out new file mode 100644 index 000000000000..e0adef1bf889 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/getter_with_wrong_return_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot return value of type "int" because the function return type is "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/property_function_get_type_error.out b/modules/gdscript/tests/scripts/analyzer/errors/property_function_get_type_error.out index 29eec51ef258..5a44a7341d28 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/property_function_get_type_error.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/property_function_get_type_error.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Function with return type "int" cannot be used as getter for a property of type "String". +Getter return type "int" is incompatible with variable type "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/property_function_set_type_error.out b/modules/gdscript/tests/scripts/analyzer/errors/property_function_set_type_error.out index 7a25280d5574..275b4511e32c 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/property_function_set_type_error.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/property_function_set_type_error.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Function with argument type "int" cannot be used as setter for a property of type "String". +Setter argument "value" of type "int" is incompatible with variable type "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/setter_with_too_few_args.gd b/modules/gdscript/tests/scripts/analyzer/errors/setter_with_too_few_args.gd new file mode 100644 index 000000000000..9b7387a751b6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/setter_with_too_few_args.gd @@ -0,0 +1,7 @@ +var a: int: set = set_named + +func test(): + pass + +func set_named() -> void: + prints("set_named") diff --git a/modules/gdscript/tests/scripts/analyzer/errors/setter_with_too_few_args.out b/modules/gdscript/tests/scripts/analyzer/errors/setter_with_too_few_args.out new file mode 100644 index 000000000000..c859963845b6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/setter_with_too_few_args.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Setters must have 1 or 2 mandatory arguments. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/setter_with_too_many_args.gd b/modules/gdscript/tests/scripts/analyzer/errors/setter_with_too_many_args.gd new file mode 100644 index 000000000000..6ba74bfbaa31 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/setter_with_too_many_args.gd @@ -0,0 +1,7 @@ +var a: int: set = set_named + +func test(): + pass + +func set_named(name: StringName, value: int, oops: int) -> void: + prints("set_named", name, value) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/setter_with_too_many_args.out b/modules/gdscript/tests/scripts/analyzer/errors/setter_with_too_many_args.out new file mode 100644 index 000000000000..c859963845b6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/setter_with_too_many_args.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Setters must have 1 or 2 mandatory arguments. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/setter_with_wrong_name_arg.gd b/modules/gdscript/tests/scripts/analyzer/errors/setter_with_wrong_name_arg.gd new file mode 100644 index 000000000000..4bf528dfc53e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/setter_with_wrong_name_arg.gd @@ -0,0 +1,7 @@ +var a: int: set = set_named + +func test(): + pass + +func set_named(name: int, value: int) -> void: + prints("set_named", name, value) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/setter_with_wrong_name_arg.out b/modules/gdscript/tests/scripts/analyzer/errors/setter_with_wrong_name_arg.out new file mode 100644 index 000000000000..f818d0a9e3c0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/setter_with_wrong_name_arg.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +First mandatory argument of a setter with 2 mandatory arguments must be a property name of type String or StringName. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/setter_with_wrong_value_arg.gd b/modules/gdscript/tests/scripts/analyzer/errors/setter_with_wrong_value_arg.gd new file mode 100644 index 000000000000..77ef20be4663 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/setter_with_wrong_value_arg.gd @@ -0,0 +1,7 @@ +var a: int: set = set_named + +func test(): + pass + +func set_named(name: StringName, value: Array) -> void: + prints("set_named", name, value) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/setter_with_wrong_value_arg.out b/modules/gdscript/tests/scripts/analyzer/errors/setter_with_wrong_value_arg.out new file mode 100644 index 000000000000..099da55fa5b5 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/setter_with_wrong_value_arg.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Setter argument "value" of type "Array" is incompatible with variable type "int". diff --git a/modules/gdscript/tests/scripts/runtime/features/setter_getter_with_name.gd b/modules/gdscript/tests/scripts/runtime/features/setter_getter_with_name.gd new file mode 100644 index 000000000000..a948af1fae6d --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/setter_getter_with_name.gd @@ -0,0 +1,171 @@ +class_name TestSetterGetterWithName + +static var static_normal_untyped: set = set_static_normal_untyped, get = get_static_normal_untyped +static var static_normal_typed: int: set = set_static_normal_typed, get = get_static_normal_typed +static var static_named_untyped: set = set_static_named_untyped, get = get_static_named_untyped +static var static_named_typed: int: set = set_static_named_typed, get = get_static_named_typed + +var normal_untyped: set = set_normal_untyped, get = get_normal_untyped +var normal_typed: int: set = set_normal_typed, get = get_normal_typed +var named_untyped: set = set_named_untyped, get = get_named_untyped +var named_typed: int: set = set_named_typed, get = get_named_typed + +static var _data := { + static_normal_untyped = 0, + static_normal_typed = 0, + static_named_untyped = 0, + static_named_typed = 0, + + normal_untyped = 0, + normal_typed = 0, + named_untyped = 0, + named_typed = 0, +} + +func check(expected: int, only_static: bool = false) -> void: + for key in _data: + if only_static and not str(key).begins_with("static_"): + continue + if _data[key] != expected: + prints("Check", expected, "is NOT correct:", key, "==", _data[key]) + return + prints("Check", expected, "is correct.") + +func test(): + var _t + + _t = static_normal_untyped + _t = static_normal_typed + _t = static_named_untyped + _t = static_named_typed + _t = normal_untyped + _t = normal_typed + _t = named_untyped + _t = named_typed + check(1) + + _t = get("static_normal_untyped") + _t = get("static_normal_typed") + _t = get("static_named_untyped") + _t = get("static_named_typed") + _t = get("normal_untyped") + _t = get("normal_typed") + _t = get("named_untyped") + _t = get("named_typed") + check(2) + + _t = TestSetterGetterWithName.static_normal_untyped + _t = TestSetterGetterWithName.static_normal_typed + _t = TestSetterGetterWithName.static_named_untyped + _t = TestSetterGetterWithName.static_named_typed + check(3, true) + + _t = (TestSetterGetterWithName as GDScript).get("static_normal_untyped") + _t = (TestSetterGetterWithName as GDScript).get("static_normal_typed") + _t = (TestSetterGetterWithName as GDScript).get("static_named_untyped") + _t = (TestSetterGetterWithName as GDScript).get("static_named_typed") + check(4, true) + + static_normal_untyped = 10 + static_normal_typed = 10 + static_named_untyped = 10 + static_named_typed = 10 + normal_untyped = 10 + normal_typed = 10 + named_untyped = 10 + named_typed = 10 + check(10) + + set("static_normal_untyped", 20) + set("static_normal_typed", 20) + set("static_named_untyped", 20) + set("static_named_typed", 20) + set("normal_untyped", 20) + set("normal_typed", 20) + set("named_untyped", 20) + set("named_typed", 20) + check(20) + + TestSetterGetterWithName.static_normal_untyped = 30 + TestSetterGetterWithName.static_normal_typed = 30 + TestSetterGetterWithName.static_named_untyped = 30 + TestSetterGetterWithName.static_named_typed = 30 + check(30, true) + + (TestSetterGetterWithName as GDScript).set("static_normal_untyped", 40) + (TestSetterGetterWithName as GDScript).set("static_normal_typed", 40) + (TestSetterGetterWithName as GDScript).set("static_named_untyped", 40) + (TestSetterGetterWithName as GDScript).set("static_named_typed", 40) + check(40, true) + +static func set_static_normal_untyped(value, optional = true): + assert(is_same(optional, true)) + _data.static_normal_untyped = value + +static func get_static_normal_untyped(optional = true): + assert(is_same(optional, true)) + _data.static_normal_untyped += 1 + return 0 + +static func set_static_normal_typed(value: int, optional: bool = true) -> void: + assert(is_same(optional, true)) + _data.static_normal_typed = value + +static func get_static_normal_typed(optional: bool = true) -> int: + assert(is_same(optional, true)) + _data.static_normal_typed += 1 + return 0 + +static func set_static_named_untyped(name, value, optional = true): + assert(is_same(optional, true)) + _data[name] = value + +static func get_static_named_untyped(name, optional = true): + assert(is_same(optional, true)) + _data[name] += 1 + return 0 + +static func set_static_named_typed(name: StringName, value: int, optional: bool = true) -> void: + assert(is_same(optional, true)) + _data[name] = value + +static func get_static_named_typed(name: StringName, optional: bool = true) -> int: + assert(is_same(optional, true)) + _data[name] += 1 + return 0 + +func set_normal_untyped(value, optional = true): + assert(is_same(optional, true)) + _data.normal_untyped = value + +func get_normal_untyped(optional = true): + assert(is_same(optional, true)) + _data.normal_untyped += 1 + return 0 + +func set_normal_typed(value: int, optional: bool = true) -> void: + assert(is_same(optional, true)) + _data.normal_typed = value + +func get_normal_typed(optional: bool = true) -> int: + assert(is_same(optional, true)) + _data.normal_typed += 1 + return 0 + +func set_named_untyped(name, value, optional = true): + assert(is_same(optional, true)) + _data[name] = value + +func get_named_untyped(name, optional = true): + assert(is_same(optional, true)) + _data[name] += 1 + return 0 + +func set_named_typed(name: StringName, value: int, optional: bool = true) -> void: + assert(is_same(optional, true)) + _data[name] = value + +func get_named_typed(name: StringName, optional: bool = true) -> int: + assert(is_same(optional, true)) + _data[name] += 1 + return 0 diff --git a/modules/gdscript/tests/scripts/runtime/features/setter_getter_with_name.out b/modules/gdscript/tests/scripts/runtime/features/setter_getter_with_name.out new file mode 100644 index 000000000000..ee5810464c97 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/setter_getter_with_name.out @@ -0,0 +1,9 @@ +GDTEST_OK +Check 1 is correct. +Check 2 is correct. +Check 3 is correct. +Check 4 is correct. +Check 10 is correct. +Check 20 is correct. +Check 30 is correct. +Check 40 is correct.