Merge pull request #82808 from dalexeev/gds-vararg

GDScript: Add support for variadic functions
This commit is contained in:
Thaddeus Crews 2025-06-09 17:08:48 -05:00
commit 0f05e91889
No known key found for this signature in database
GPG key ID: 8C6E5FEB5FC03CCC
33 changed files with 416 additions and 65 deletions

View file

@ -107,6 +107,8 @@ public:
bool is_experimental = false;
String experimental_message;
Vector<ArgumentDoc> arguments;
// NOTE: Only for GDScript for now. The rest argument is not saved to the XML file.
ArgumentDoc rest_argument;
Vector<int> errors_returned;
String keywords;
bool operator<(const MethodDoc &p_method) const {

View file

@ -314,12 +314,15 @@ List<MethodInfo> ConnectDialog::_filter_method_list(const List<MethodInfo> &p_me
}
if (check_signal) {
if (mi.arguments.size() != effective_args.size()) {
const unsigned min_argc = mi.arguments.size() - mi.default_arguments.size();
const unsigned max_argc = (mi.flags & METHOD_FLAG_VARARG) ? UINT_MAX : mi.arguments.size();
if (effective_args.size() < min_argc || effective_args.size() > max_argc) {
continue;
}
bool type_mismatch = false;
for (int64_t i = 0; i < mi.arguments.size(); ++i) {
for (int64_t i = 0; i < effective_args.size() && i < mi.arguments.size(); ++i) {
Variant::Type stype = effective_args[i].first;
Variant::Type mtype = mi.arguments[i].type;
@ -613,6 +616,10 @@ String ConnectDialog::get_signature(const MethodInfo &p_method, PackedStringArra
}
}
if (p_method.flags & METHOD_FLAG_VARARG) {
signature.append(p_method.arguments.is_empty() ? "..." : ", ...");
}
signature.append(")");
return String().join(signature);
}
@ -1182,9 +1189,9 @@ bool ConnectionsDock::_is_connection_inherited(Connection &p_connection) {
* Open connection dialog with TreeItem data to CREATE a brand-new connection.
*/
void ConnectionsDock::_open_connection_dialog(TreeItem &p_item) {
Dictionary sinfo = p_item.get_metadata(0);
String signal_name = sinfo["name"];
PackedStringArray signal_args = sinfo["args"];
const Dictionary sinfo = p_item.get_metadata(0);
const StringName signal_name = sinfo["name"];
const PackedStringArray signal_args = sinfo["args"];
Node *dst_node = selected_node->get_owner() ? selected_node->get_owner() : selected_node;
if (!dst_node || dst_node->get_script().is_null()) {
@ -1193,12 +1200,12 @@ void ConnectionsDock::_open_connection_dialog(TreeItem &p_item) {
ConnectDialog::ConnectionData cd;
cd.source = selected_node;
cd.signal = StringName(signal_name);
cd.signal = signal_name;
cd.target = dst_node;
cd.method = ConnectDialog::generate_method_callback_name(cd.source, signal_name, cd.target);
connect_dialog->init(cd, signal_args);
connect_dialog->set_title(TTR("Connect a Signal to a Method"));
connect_dialog->popup_dialog(signal_name + "(" + String(", ").join(signal_args) + ")");
connect_dialog->popup_dialog(signal_name.operator String() + "(" + String(", ").join(signal_args) + ")");
}
/*
@ -1215,12 +1222,12 @@ void ConnectionsDock::_open_edit_connection_dialog(TreeItem &p_item) {
Node *dst = Object::cast_to<Node>(cd.target);
if (src && dst) {
const String &signal_name_ref = cd.signal;
PackedStringArray signal_args = signal_item->get_metadata(0).operator Dictionary()["args"];
const StringName &signal_name = cd.signal;
const PackedStringArray signal_args = signal_item->get_metadata(0).operator Dictionary()["args"];
connect_dialog->set_title(vformat(TTR("Edit Connection: '%s'"), cd.signal));
connect_dialog->popup_dialog(signal_name_ref);
connect_dialog->init(cd, signal_args, true);
connect_dialog->set_title(vformat(TTR("Edit Connection: '%s'"), cd.signal));
connect_dialog->popup_dialog(signal_name.operator String() + "(" + String(", ").join(signal_args) + ")");
}
}

View file

@ -582,14 +582,20 @@ void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview
for (int j = 0; j < p_method.arguments.size(); j++) {
const DocData::ArgumentDoc &argument = p_method.arguments[j];
class_desc->push_color(theme_cache.text_color);
if (j > 0) {
class_desc->push_color(theme_cache.symbol_color);
class_desc->add_text(", ");
class_desc->pop(); // color
}
class_desc->push_color(theme_cache.text_color);
class_desc->add_text(argument.name);
class_desc->pop(); // color
class_desc->push_color(theme_cache.symbol_color);
class_desc->add_text(colon_nbsp);
class_desc->pop(); // color
_add_type(argument.type, argument.enumeration, argument.is_bitfield);
if (!argument.default_value.is_empty()) {
@ -601,13 +607,11 @@ void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview
class_desc->add_text(_fix_constant(argument.default_value));
class_desc->pop(); // color
}
class_desc->pop(); // color
}
if (is_vararg) {
if (!p_method.arguments.is_empty()) {
class_desc->push_color(theme_cache.text_color);
class_desc->push_color(theme_cache.symbol_color);
class_desc->add_text(", ");
class_desc->pop(); // color
}
@ -615,6 +619,22 @@ void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview
class_desc->push_color(theme_cache.symbol_color);
class_desc->add_text("...");
class_desc->pop(); // color
const DocData::ArgumentDoc &rest_argument = p_method.rest_argument;
class_desc->push_color(theme_cache.text_color);
class_desc->add_text(rest_argument.name.is_empty() ? "args" : rest_argument.name);
class_desc->pop(); // color
class_desc->push_color(theme_cache.symbol_color);
class_desc->add_text(colon_nbsp);
class_desc->pop(); // color
if (rest_argument.type.is_empty()) {
_add_type("Array");
} else {
_add_type(rest_argument.type, rest_argument.enumeration, rest_argument.is_bitfield);
}
}
class_desc->push_color(theme_cache.symbol_color);
@ -1558,14 +1578,20 @@ void EditorHelp::_update_doc() {
for (int j = 0; j < signal.arguments.size(); j++) {
const DocData::ArgumentDoc &argument = signal.arguments[j];
class_desc->push_color(theme_cache.text_color);
if (j > 0) {
class_desc->push_color(theme_cache.symbol_color);
class_desc->add_text(", ");
class_desc->pop(); // color
}
class_desc->push_color(theme_cache.text_color);
class_desc->add_text(argument.name);
class_desc->pop(); // color
class_desc->push_color(theme_cache.symbol_color);
class_desc->add_text(colon_nbsp);
class_desc->pop(); // color
_add_type(argument.type, argument.enumeration, argument.is_bitfield);
// Signals currently do not support default argument values, neither the core nor GDScript.
@ -1579,8 +1605,6 @@ void EditorHelp::_update_doc() {
class_desc->add_text(_fix_constant(argument.default_value));
class_desc->pop(); // color
}
class_desc->pop(); // color
}
class_desc->push_color(theme_cache.symbol_color);
@ -2002,14 +2026,20 @@ void EditorHelp::_update_doc() {
for (int j = 0; j < annotation.arguments.size(); j++) {
const DocData::ArgumentDoc &argument = annotation.arguments[j];
class_desc->push_color(theme_cache.text_color);
if (j > 0) {
class_desc->push_color(theme_cache.symbol_color);
class_desc->add_text(", ");
class_desc->pop(); // color
}
class_desc->push_color(theme_cache.text_color);
class_desc->add_text(argument.name);
class_desc->pop(); // color
class_desc->push_color(theme_cache.symbol_color);
class_desc->add_text(colon_nbsp);
class_desc->pop(); // color
_add_type(argument.type, argument.enumeration, argument.is_bitfield);
if (!argument.default_value.is_empty()) {
@ -2021,13 +2051,11 @@ void EditorHelp::_update_doc() {
class_desc->add_text(_fix_constant(argument.default_value));
class_desc->pop(); // color
}
class_desc->pop(); // color
}
if (annotation.qualifiers.contains("vararg")) {
if (!annotation.arguments.is_empty()) {
class_desc->push_color(theme_cache.text_color);
class_desc->push_color(theme_cache.symbol_color);
class_desc->add_text(", ");
class_desc->pop(); // color
}
@ -2035,6 +2063,22 @@ void EditorHelp::_update_doc() {
class_desc->push_color(theme_cache.symbol_color);
class_desc->add_text("...");
class_desc->pop(); // color
const DocData::ArgumentDoc &rest_argument = annotation.rest_argument;
class_desc->push_color(theme_cache.text_color);
class_desc->add_text(rest_argument.name.is_empty() ? "args" : rest_argument.name);
class_desc->pop(); // color
class_desc->push_color(theme_cache.symbol_color);
class_desc->add_text(colon_nbsp);
class_desc->pop(); // color
if (rest_argument.type.is_empty()) {
_add_type("Array");
} else {
_add_type(rest_argument.type, rest_argument.enumeration, rest_argument.is_bitfield);
}
}
class_desc->push_color(theme_cache.symbol_color);
@ -3730,10 +3774,13 @@ EditorHelpBit::HelpData EditorHelpBit::_get_method_help_data(const StringName &p
}
current.doc_type = { method.return_type, method.return_enum, method.return_is_bitfield };
for (const DocData::ArgumentDoc &argument : method.arguments) {
const DocType argument_type = { argument.type, argument.enumeration, argument.is_bitfield };
current.arguments.push_back({ argument.name, argument_type, argument.default_value });
const DocType argument_doc_type = { argument.type, argument.enumeration, argument.is_bitfield };
current.arguments.push_back({ argument.name, argument_doc_type, argument.default_value });
}
current.qualifiers = method.qualifiers;
const DocData::ArgumentDoc &rest_argument = method.rest_argument;
const DocType rest_argument_doc_type = { rest_argument.type, rest_argument.enumeration, rest_argument.is_bitfield };
current.rest_argument = { rest_argument.name, rest_argument_doc_type, rest_argument.default_value };
if (method.name == p_method_name) {
result = current;
@ -3895,6 +3942,7 @@ void EditorHelpBit::_update_labels() {
title->pop(); // font
const Color text_color = get_theme_color(SNAME("text_color"), SNAME("EditorHelp"));
const Color symbol_color = get_theme_color(SNAME("symbol_color"), SNAME("EditorHelp"));
const Color value_color = get_theme_color(SNAME("value_color"), SNAME("EditorHelp"));
const Color qualifier_color = get_theme_color(SNAME("qualifier_color"), SNAME("EditorHelp"));
@ -3970,10 +4018,14 @@ void EditorHelpBit::_update_labels() {
const ArgumentData &argument = help_data.arguments[i];
if (i > 0) {
title->push_color(symbol_color);
title->add_text(", ");
title->pop(); // color
}
title->push_color(text_color);
title->add_text(argument.name);
title->pop(); // color
title->push_color(symbol_color);
title->add_text(colon_nbsp);
@ -3994,12 +4046,30 @@ void EditorHelpBit::_update_labels() {
if (help_data.qualifiers.contains("vararg")) {
if (!help_data.arguments.is_empty()) {
title->push_color(symbol_color);
title->add_text(", ");
title->pop(); // color
}
title->push_color(symbol_color);
title->add_text("...");
title->pop(); // color
const ArgumentData &rest_argument = help_data.rest_argument;
title->push_color(text_color);
title->add_text(rest_argument.name.is_empty() ? "args" : rest_argument.name);
title->pop(); // color
title->push_color(symbol_color);
title->add_text(colon_nbsp);
title->pop(); // color
if (rest_argument.doc_type.type.is_empty()) {
_add_type_to_title({ "Array", "", false });
} else {
_add_type_to_title(rest_argument.doc_type);
}
}
title->push_color(symbol_color);

View file

@ -302,6 +302,7 @@ class EditorHelpBit : public VBoxContainer {
DocType doc_type;
String value;
Vector<ArgumentData> arguments;
ArgumentData rest_argument;
String qualifiers;
String resource_path;
};

View file

@ -287,8 +287,16 @@ void PropertySelector::_update_search() {
}
}
if (mi.flags & METHOD_FLAG_VARARG) {
desc += mi.arguments.is_empty() ? "..." : ", ...";
}
desc += ")";
if (mi.flags & METHOD_FLAG_VARARG) {
desc += " vararg";
}
if (mi.flags & METHOD_FLAG_CONST) {
desc += " const";
}

View file

@ -408,13 +408,25 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
method_doc.is_experimental = m_func->doc_data.is_experimental;
method_doc.experimental_message = m_func->doc_data.experimental_message;
// Currently, an abstract function cannot be static.
if (m_func->is_vararg()) {
if (!method_doc.qualifiers.is_empty()) {
method_doc.qualifiers += " ";
}
method_doc.qualifiers += "vararg";
method_doc.rest_argument.name = m_func->rest_parameter->identifier->name;
_doctype_from_gdtype(m_func->rest_parameter->get_datatype(), method_doc.rest_argument.type, method_doc.rest_argument.enumeration);
}
if (m_func->is_abstract) {
method_doc.qualifiers = "abstract";
} else if (m_func->is_static) {
method_doc.qualifiers = "static";
} else {
method_doc.qualifiers = "";
if (!method_doc.qualifiers.is_empty()) {
method_doc.qualifiers += " ";
}
method_doc.qualifiers += "abstract";
}
if (m_func->is_static) {
if (!method_doc.qualifiers.is_empty()) {
method_doc.qualifiers += " ";
}
method_doc.qualifiers += "static";
}
if (func_name == "_init") {

View file

@ -492,7 +492,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
k--;
}
if (str[k] == '.') {
if (str[k] == '.' && (k < 1 || str[k - 1] != '.')) {
in_member_variable = true;
}
}

View file

@ -1798,6 +1798,33 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
}
}
if (p_function->is_vararg()) {
resolve_parameter(p_function->rest_parameter);
if (p_function->rest_parameter->datatype_specifier != nullptr) {
GDScriptParser::DataType specified_type = p_function->rest_parameter->get_datatype();
if (specified_type.kind != GDScriptParser::DataType::BUILTIN || specified_type.builtin_type != Variant::ARRAY) {
push_error(vformat(R"(The rest parameter type must be "Array", but "%s" is specified.)", specified_type.to_string()), p_function->rest_parameter->datatype_specifier);
} else if ((specified_type.has_container_element_type(0) && !specified_type.get_container_element_type(0).is_variant())) {
push_error(R"(Typed arrays are currently not supported for the rest parameter.)", p_function->rest_parameter->datatype_specifier);
}
} else {
GDScriptParser::DataType inferred_type;
inferred_type.type_source = GDScriptParser::DataType::INFERRED;
inferred_type.kind = GDScriptParser::DataType::BUILTIN;
inferred_type.builtin_type = Variant::ARRAY;
p_function->rest_parameter->set_datatype(inferred_type);
#ifdef DEBUG_ENABLED
parser->push_warning(p_function->rest_parameter, GDScriptWarning::UNTYPED_DECLARATION, "Parameter", p_function->rest_parameter->identifier->name);
#endif
}
#ifdef DEBUG_ENABLED
if (p_function->rest_parameter->usages == 0 && !String(p_function->rest_parameter->identifier->name).begins_with("_") && !p_function->is_abstract) {
parser->push_warning(p_function->rest_parameter->identifier, GDScriptWarning::UNUSED_PARAMETER, function_visible_name, p_function->rest_parameter->identifier->name);
}
is_shadowing(p_function->rest_parameter->identifier, "function parameter", true);
#endif // DEBUG_ENABLED
}
if (!p_is_lambda && function_name == GDScriptLanguage::get_singleton()->strings._init) {
// Constructor.
GDScriptParser::DataType return_type = parser->current_class->get_datatype();
@ -1864,15 +1891,23 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
}
}
int par_count_diff = p_function->parameters.size() - parameters_types.size();
valid = valid && par_count_diff >= 0;
valid = valid && default_value_count >= default_par_count + par_count_diff;
int parent_min_argc = parameters_types.size() - default_par_count;
int parent_max_argc = (method_flags & METHOD_FLAG_VARARG) ? INT_MAX : parameters_types.size();
int current_min_argc = p_function->parameters.size() - default_value_count;
int current_max_argc = p_function->is_vararg() ? INT_MAX : p_function->parameters.size();
// `[current_min_argc..current_max_argc]` must include `[parent_min_argc..parent_max_argc]`.
valid = valid && current_min_argc <= parent_min_argc && parent_max_argc <= current_max_argc;
if (valid) {
int i = 0;
for (const GDScriptParser::DataType &parent_par_type : parameters_types) {
if (i >= p_function->parameters.size()) {
break;
}
const GDScriptParser::DataType &current_par_type = p_function->parameters[i]->datatype;
i++;
// Check parameter type contravariance.
GDScriptParser::DataType current_par_type = p_function->parameters[i++]->get_datatype();
if (parent_par_type.is_variant() && parent_par_type.is_hard_type()) {
// `is_type_compatible()` returns `true` if one of the types is `Variant`.
// Don't allow narrowing a hard `Variant`.
@ -1902,6 +1937,12 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
j++;
}
if (method_flags & METHOD_FLAG_VARARG) {
if (!parameters_types.is_empty()) {
parent_signature += ", ";
}
parent_signature += "...";
}
parent_signature += ") -> ";
const String return_type = parent_return_type.to_string_strict();
@ -5791,6 +5832,9 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo
r_default_arg_count++;
}
}
if (found_function->is_vararg()) {
r_method_flags.set_flag(METHOD_FLAG_VARARG);
}
r_return_type = p_is_constructor ? p_base_type : found_function->get_datatype();
r_return_type.is_meta_type = false;
r_return_type.is_coroutine = found_function->is_coroutine;

View file

@ -2338,6 +2338,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
codegen.generator->write_start(p_script, func_name, is_static, rpc_config, return_type);
int optional_parameters = 0;
GDScriptCodeGenerator::Address vararg_addr;
if (p_func) {
for (int i = 0; i < p_func->parameters.size(); i++) {
@ -2353,6 +2354,11 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
}
}
if (p_func->is_vararg()) {
vararg_addr = codegen.add_local(p_func->rest_parameter->identifier->name, _gdtype_from_datatype(p_func->rest_parameter->get_datatype(), codegen.script));
method_info.flags |= METHOD_FLAG_VARARG;
}
method_info.default_arguments.append_array(p_func->default_arg_values);
}
@ -2519,6 +2525,10 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
gd_function->return_type.kind = GDScriptDataType::BUILTIN;
gd_function->return_type.builtin_type = Variant::NIL;
}
if (p_func->is_vararg()) {
gd_function->_vararg_index = vararg_addr.address;
}
}
gd_function->method_info = method_info;

View file

@ -778,7 +778,7 @@ static String _make_arguments_hint(const MethodInfo &p_info, int p_arg_idx, bool
if (p_arg_idx >= p_info.arguments.size()) {
arghint += String::chr(0xFFFF);
}
arghint += "...";
arghint += "...args: Array"; // `MethodInfo` does not support the rest parameter name.
if (p_arg_idx >= p_info.arguments.size()) {
arghint += String::chr(0xFFFF);
}
@ -796,9 +796,9 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio
arghint = "(";
} else {
if (p_function->get_datatype().builtin_type == Variant::NIL) {
arghint = "void " + p_function->identifier->name.operator String() + "(";
arghint = "void " + p_function->identifier->name + "(";
} else {
arghint = p_function->get_datatype().to_string() + " " + p_function->identifier->name.operator String() + "(";
arghint = p_function->get_datatype().to_string() + " " + p_function->identifier->name + "(";
}
}
@ -870,6 +870,20 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio
}
}
if (p_function->is_vararg()) {
if (!p_function->parameters.is_empty()) {
arghint += ", ";
}
if (p_arg_idx >= p_function->parameters.size()) {
arghint += String::chr(0xFFFF);
}
const GDScriptParser::ParameterNode *rest_param = p_function->rest_parameter;
arghint += "..." + rest_param->identifier->name + ": " + rest_param->get_datatype().to_string();
if (p_arg_idx >= p_function->parameters.size()) {
arghint += String::chr(0xFFFF);
}
}
arghint += ")";
return arghint;
@ -3610,6 +3624,15 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
method_hint += ": " + _get_visual_datatype(mi.arguments[i], true, class_name);
}
}
if (mi.flags & METHOD_FLAG_VARARG) {
if (!mi.arguments.is_empty()) {
method_hint += ", ";
}
method_hint += "...args"; // `MethodInfo` does not support the rest parameter name.
if (use_type_hint) {
method_hint += ": Array";
}
}
method_hint += ")";
if (use_type_hint) {
method_hint += " -> " + _get_visual_datatype(mi.return_val, false, class_name);

View file

@ -248,8 +248,9 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) {
void GDScriptFunctionState::_clear_stack() {
if (state.stack_size) {
Variant *stack = (Variant *)state.stack.ptr();
// The first 3 are special addresses and not copied to the state, so we skip them here.
for (int i = 3; i < state.stack_size; i++) {
// First `GDScriptFunction::FIXED_ADDRESSES_MAX` stack addresses are special
// and not copied to the state, so we skip them here.
for (int i = GDScriptFunction::FIXED_ADDRESSES_MAX; i < state.stack_size; i++) {
stack[i].~Variant();
}
state.stack_size = 0;

View file

@ -458,6 +458,7 @@ private:
GDScript *_script = nullptr;
int _initial_line = 0;
int _argument_count = 0;
int _vararg_index = -1;
int _stack_size = 0;
int _instruction_args_size = 0;
@ -579,6 +580,7 @@ public:
_FORCE_INLINE_ StringName get_source() const { return source; }
_FORCE_INLINE_ GDScript *get_script() const { return _script; }
_FORCE_INLINE_ bool is_static() const { return _static; }
_FORCE_INLINE_ bool is_vararg() const { return _vararg_index >= 0; }
_FORCE_INLINE_ MethodInfo get_method_info() const { return method_info; }
_FORCE_INLINE_ int get_argument_count() const { return _argument_count; }
_FORCE_INLINE_ Variant get_rpc_config() const { return rpc_config; }

View file

@ -1632,20 +1632,40 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod
// Allow for trailing comma.
break;
}
bool is_rest = false;
if (match(GDScriptTokenizer::Token::PERIOD_PERIOD_PERIOD)) {
is_rest = true;
}
ParameterNode *parameter = parse_parameter();
if (parameter == nullptr) {
break;
}
if (p_function->is_vararg()) {
push_error("Cannot have parameters after the rest parameter.");
continue;
}
if (parameter->initializer != nullptr) {
if (is_rest) {
push_error("The rest parameter cannot have a default value.");
continue;
}
default_used = true;
} else {
if (default_used) {
if (default_used && !is_rest) {
push_error("Cannot have mandatory parameters after optional parameters.");
continue;
}
}
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));
} else if (is_rest) {
p_function->rest_parameter = parameter;
p_body->add_local(parameter, current_function);
} else {
p_function->parameters_indices[parameter->identifier->name] = p_function->parameters.size();
p_function->parameters.push_back(parameter);
@ -1669,7 +1689,7 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod
if (!p_function->is_static) {
push_error(R"(Static constructor must be declared static.)");
}
if (p_function->parameters.size() != 0) {
if (!p_function->parameters.is_empty() || p_function->is_vararg()) {
push_error(R"(Static constructor cannot have parameters.)");
}
current_class->has_static_data = true;
@ -4233,6 +4253,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty
{ nullptr, nullptr, PREC_NONE }, // SEMICOLON,
{ nullptr, &GDScriptParser::parse_attribute, PREC_ATTRIBUTE }, // PERIOD,
{ nullptr, nullptr, PREC_NONE }, // PERIOD_PERIOD,
{ nullptr, nullptr, PREC_NONE }, // PERIOD_PERIOD_PERIOD,
{ nullptr, nullptr, PREC_NONE }, // COLON,
{ &GDScriptParser::parse_get_node, nullptr, PREC_NONE }, // DOLLAR,
{ nullptr, nullptr, PREC_NONE }, // FORWARD_ARROW,

View file

@ -851,6 +851,7 @@ public:
IdentifierNode *identifier = nullptr;
Vector<ParameterNode *> parameters;
HashMap<StringName, int> parameters_indices;
ParameterNode *rest_parameter = nullptr;
TypeNode *return_type = nullptr;
SuiteNode *body = nullptr;
bool is_abstract = false;
@ -869,6 +870,8 @@ public:
bool resolved_signature = false;
bool resolved_body = false;
_FORCE_INLINE_ bool is_vararg() const { return rest_parameter != nullptr; }
FunctionNode() {
type = FUNCTION;
}

View file

@ -135,6 +135,7 @@ static const char *token_names[] = {
";", // SEMICOLON,
".", // PERIOD,
"..", // PERIOD_PERIOD,
"...", // PERIOD_PERIOD_PERIOD,
":", // COLON,
"$", // DOLLAR,
"->", // FORWARD_ARROW,
@ -1501,6 +1502,10 @@ GDScriptTokenizer::Token GDScriptTokenizerText::scan() {
case '.':
if (_peek() == '.') {
_advance();
if (_peek() == '.') {
_advance();
return make_token(Token::PERIOD_PERIOD_PERIOD);
}
return make_token(Token::PERIOD_PERIOD);
} else if (is_digit(_peek())) {
// Number starting with '.'.

View file

@ -139,6 +139,7 @@ public:
SEMICOLON,
PERIOD,
PERIOD_PERIOD,
PERIOD_PERIOD_PERIOD,
COLON,
DOLLAR,
FORWARD_ARROW,

View file

@ -554,10 +554,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
} else {
if (p_argcount != _argument_count) {
if (p_argcount > _argument_count) {
if (!is_vararg()) {
r_err.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
r_err.expected = _argument_count;
call_depth--;
return _get_default_variant_for_data_type(return_type);
}
} else if (p_argcount < _argument_count - _default_arg_count) {
r_err.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_err.expected = _argument_count - _default_arg_count;
@ -568,21 +570,21 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
}
// Add 3 here for self, class, and nil.
alloca_size = sizeof(Variant *) * 3 + sizeof(Variant *) * _instruction_args_size + sizeof(Variant) * _stack_size;
alloca_size = sizeof(Variant *) * FIXED_ADDRESSES_MAX + sizeof(Variant *) * _instruction_args_size + sizeof(Variant) * _stack_size;
uint8_t *aptr = (uint8_t *)alloca(alloca_size);
stack = (Variant *)aptr;
for (int i = 0; i < p_argcount; i++) {
const int non_vararg_arg_count = MIN(p_argcount, _argument_count);
for (int i = 0; i < non_vararg_arg_count; i++) {
if (!argument_types[i].has_type) {
memnew_placement(&stack[i + 3], Variant(*p_args[i]));
memnew_placement(&stack[i + FIXED_ADDRESSES_MAX], Variant(*p_args[i]));
continue;
}
// If types already match, don't call Variant::construct(). Constructors of some types
// (e.g. packed arrays) do copies, whereas they pass by reference when inside a Variant.
if (argument_types[i].is_type(*p_args[i], false)) {
memnew_placement(&stack[i + 3], Variant(*p_args[i]));
memnew_placement(&stack[i + FIXED_ADDRESSES_MAX], Variant(*p_args[i]));
continue;
}
if (!argument_types[i].is_type(*p_args[i], true)) {
@ -597,11 +599,11 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
const GDScriptDataType &arg_key_type = argument_types[i].get_container_element_type_or_variant(0);
const GDScriptDataType &arg_value_type = argument_types[i].get_container_element_type_or_variant(1);
Dictionary dict(p_args[i]->operator Dictionary(), arg_key_type.builtin_type, arg_key_type.native_type, arg_key_type.script_type, arg_value_type.builtin_type, arg_value_type.native_type, arg_value_type.script_type);
memnew_placement(&stack[i + 3], Variant(dict));
memnew_placement(&stack[i + FIXED_ADDRESSES_MAX], Variant(dict));
} else if (argument_types[i].builtin_type == Variant::ARRAY && argument_types[i].has_container_element_type(0)) {
const GDScriptDataType &arg_type = argument_types[i].container_element_types[0];
Array array(p_args[i]->operator Array(), arg_type.builtin_type, arg_type.native_type, arg_type.script_type);
memnew_placement(&stack[i + 3], Variant(array));
memnew_placement(&stack[i + FIXED_ADDRESSES_MAX], Variant(array));
} else {
Variant variant;
Variant::construct(argument_types[i].builtin_type, variant, &p_args[i], 1, r_err);
@ -612,16 +614,27 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
call_depth--;
return _get_default_variant_for_data_type(return_type);
}
memnew_placement(&stack[i + 3], Variant(variant));
memnew_placement(&stack[i + FIXED_ADDRESSES_MAX], Variant(variant));
}
} else {
memnew_placement(&stack[i + 3], Variant(*p_args[i]));
memnew_placement(&stack[i + FIXED_ADDRESSES_MAX], Variant(*p_args[i]));
}
}
for (int i = p_argcount + 3; i < _stack_size; i++) {
for (int i = non_vararg_arg_count + FIXED_ADDRESSES_MAX; i < _stack_size; i++) {
memnew_placement(&stack[i], Variant);
}
if (is_vararg()) {
Array vararg;
stack[_vararg_index] = vararg;
if (p_argcount > _argument_count) {
vararg.resize(p_argcount - _argument_count);
for (int i = 0; i < p_argcount - _argument_count; i++) {
vararg[i] = *p_args[i + _argument_count];
}
}
}
if (_instruction_args_size) {
instruction_args = (Variant **)&aptr[sizeof(Variant) * _stack_size];
} else {
@ -2559,8 +2572,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
gdfs->state.stack.resize(alloca_size);
// First 3 stack addresses are special, so we just skip them here.
for (int i = 3; i < _stack_size; i++) {
// First `FIXED_ADDRESSES_MAX` stack addresses are special, so we just skip them here.
for (int i = FIXED_ADDRESSES_MAX; i < _stack_size; i++) {
memnew_placement(&gdfs->state.stack.write[sizeof(Variant) * i], Variant(stack[i]));
}
gdfs->state.stack_size = _stack_size;

View file

@ -508,9 +508,22 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN
parameters += " = " + parameter->initializer->reduced_value.to_json_string();
}
}
if (p_func->is_vararg()) {
if (!p_func->parameters.is_empty()) {
parameters += ", ";
}
const ParameterNode *rest_param = p_func->rest_parameter;
parameters += "..." + rest_param->identifier->name + ": " + rest_param->get_datatype().to_string();
}
r_symbol.detail += parameters + ")";
if (p_func->get_datatype().is_hard_type()) {
r_symbol.detail += " -> " + p_func->get_datatype().to_string();
const DataType return_type = p_func->get_datatype();
if (return_type.is_hard_type()) {
if (return_type.kind == DataType::BUILTIN && return_type.builtin_type == Variant::NIL) {
r_symbol.detail += " -> void";
} else {
r_symbol.detail += " -> " + return_type.to_string();
}
}
List<GDScriptParser::SuiteNode *> function_nodes;

View file

@ -0,0 +1,22 @@
class A:
func f1(x: int, ...args: Array) -> void:
prints(x, args)
func f2(x: int, ...args: Array) -> void:
prints(x, args)
class B extends A:
func f1(x: int, y: int, ...args: Array) -> void:
prints(x, y, args)
func f2(x: int) -> void:
print(x)
func g(...args: int):
pass
func h(...args: Array[int]):
pass
func test():
pass

View file

@ -0,0 +1,5 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 15: The rest parameter type must be "Array", but "int" is specified.
>> ERROR at line 18: Typed arrays are currently not supported for the rest parameter.
>> ERROR at line 9: The function signature doesn't match the parent. Parent signature is "f1(int, ...) -> void".
>> ERROR at line 12: The function signature doesn't match the parent. Parent signature is "f2(int, ...) -> void".

View file

@ -0,0 +1,5 @@
func f(...args, extra_arg):
pass
func test():
pass

View file

@ -0,0 +1,2 @@
GDTEST_PARSER_ERROR
Cannot have parameters after the rest parameter.

View file

@ -0,0 +1,5 @@
func f(...args, ...more_args):
pass
func test():
pass

View file

@ -0,0 +1,2 @@
GDTEST_PARSER_ERROR
Cannot have parameters after the rest parameter.

View file

@ -0,0 +1,5 @@
func f(...args = []):
pass
func test():
pass

View file

@ -0,0 +1,2 @@
GDTEST_PARSER_ERROR
The rest parameter cannot have a default value.

View file

@ -0,0 +1,5 @@
static func _static_init(...args) -> void:
pass
func test():
pass

View file

@ -0,0 +1,2 @@
GDTEST_PARSER_ERROR
Static constructor cannot have parameters.

View file

@ -4,6 +4,7 @@ abstract class A:
# No `UNUSED_PARAMETER` warning.
abstract func func_with_param(param: int) -> int
abstract func func_with_rest_param(...args: Array) -> int
abstract func func_with_semicolon() -> int;
abstract func func_1() -> int; abstract func func_2() -> int
abstract func func_without_return_type()
@ -23,6 +24,7 @@ class C extends B:
return "text_2c"
func func_with_param(param: int) -> int: return param
func func_with_rest_param(...args: Array) -> int: return args.size()
func func_with_semicolon() -> int: return 0
func func_1() -> int: return 0
func func_2() -> int: return 0

View file

@ -0,0 +1,38 @@
class A:
func f(x: int) -> void:
print(x)
class B extends A:
func f(x: int, ...args: Array) -> void:
prints(x, args)
class C extends B:
func f(x: int, y: int = 0, ...args: Array) -> void:
prints(x, y, args)
class D extends C:
func f(...args: Array) -> void:
print(args)
func test_func(x: int, y: int = 0, ...args: Array) -> void:
prints(x, y, args)
var test_lambda := func (x: int, y: int = 0, ...args: Array) -> void:
prints(x, y, args)
func test():
for method in get_method_list():
if str(method.name).begins_with("test_"):
print(Utils.get_method_signature(method))
test_func(1)
test_func(1, 2)
test_func(1, 2, 3)
test_func(1, 2, 3, 4)
test_func(1, 2, 3, 4, 5)
test_lambda.call(1)
test_lambda.call(1, 2)
test_lambda.call(1, 2, 3)
test_lambda.call(1, 2, 3, 4)
test_lambda.call(1, 2, 3, 4, 5)

View file

@ -0,0 +1,12 @@
GDTEST_OK
func test_func(x: int, y: int = 0, ...args) -> void
1 0 []
1 2 []
1 2 [3]
1 2 [3, 4]
1 2 [3, 4, 5]
1 0 []
1 2 []
1 2 [3]
1 2 [3, 4]
1 2 [3, 4, 5]

View file

@ -117,6 +117,10 @@ static func get_method_signature(method: Dictionary, is_signal: bool = false) ->
if i >= mandatory_argc:
result += " = " + var_to_str(default_args[i - mandatory_argc])
if method.flags & METHOD_FLAG_VARARG:
# `MethodInfo` does not support the rest parameter name.
result += "...args" if args.is_empty() else ", ...args"
result += ")"
if is_signal:
if get_type(method.return, true) != "void":

View file

@ -207,6 +207,10 @@ static void disassemble_function(const GDScriptFunction *p_func, const Vector<St
arg_string += arg_info.name;
is_first_arg = false;
}
if (p_func->is_vararg()) {
// `MethodInfo` does not support the rest parameter name.
arg_string += (p_func->get_argument_count() == 0) ? "...args" : ", ...args";
}
print_line(vformat("Function %s(%s)", p_func->get_name(), arg_string));
#ifdef TOOLS_ENABLED