From ee121ef80e36865ac9d5c55ab2ec419f48ef6954 Mon Sep 17 00:00:00 2001 From: Danil Alexeev Date: Sun, 30 Mar 2025 12:59:05 +0300 Subject: [PATCH] GDScript: Add support for variadic functions --- core/doc_data.h | 2 + editor/connections_dialog.cpp | 33 +++--- editor/editor_help.cpp | 102 +++++++++++++++--- editor/editor_help.h | 1 + editor/property_selector.cpp | 8 ++ modules/gdscript/editor/gdscript_docgen.cpp | 24 +++-- .../gdscript/editor/gdscript_highlighter.cpp | 2 +- modules/gdscript/gdscript_analyzer.cpp | 52 ++++++++- modules/gdscript/gdscript_compiler.cpp | 10 ++ modules/gdscript/gdscript_editor.cpp | 29 ++++- modules/gdscript/gdscript_function.cpp | 5 +- modules/gdscript/gdscript_function.h | 2 + modules/gdscript/gdscript_parser.cpp | 25 ++++- modules/gdscript/gdscript_parser.h | 3 + modules/gdscript/gdscript_tokenizer.cpp | 5 + modules/gdscript/gdscript_tokenizer.h | 1 + modules/gdscript/gdscript_vm.cpp | 45 +++++--- .../gdscript_extend_parser.cpp | 17 ++- .../analyzer/errors/variadic_functions.gd | 22 ++++ .../analyzer/errors/variadic_functions.out | 5 + .../errors/variadic_func_params_after_rest.gd | 5 + .../variadic_func_params_after_rest.out | 2 + .../errors/variadic_func_rest_after_rest.gd | 5 + .../errors/variadic_func_rest_after_rest.out | 2 + .../errors/variadic_func_rest_with_default.gd | 5 + .../variadic_func_rest_with_default.out | 2 + .../errors/variadic_func_static_init.gd | 5 + .../errors/variadic_func_static_init.out | 2 + .../runtime/features/abstract_methods.gd | 2 + .../runtime/features/variadic_functions.gd | 38 +++++++ .../runtime/features/variadic_functions.out | 12 +++ .../gdscript/tests/scripts/utils.notest.gd | 4 + modules/gdscript/tests/test_gdscript.cpp | 4 + 33 files changed, 416 insertions(+), 65 deletions(-) create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/variadic_functions.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/variadic_functions.out create mode 100644 modules/gdscript/tests/scripts/parser/errors/variadic_func_params_after_rest.gd create mode 100644 modules/gdscript/tests/scripts/parser/errors/variadic_func_params_after_rest.out create mode 100644 modules/gdscript/tests/scripts/parser/errors/variadic_func_rest_after_rest.gd create mode 100644 modules/gdscript/tests/scripts/parser/errors/variadic_func_rest_after_rest.out create mode 100644 modules/gdscript/tests/scripts/parser/errors/variadic_func_rest_with_default.gd create mode 100644 modules/gdscript/tests/scripts/parser/errors/variadic_func_rest_with_default.out create mode 100644 modules/gdscript/tests/scripts/parser/errors/variadic_func_static_init.gd create mode 100644 modules/gdscript/tests/scripts/parser/errors/variadic_func_static_init.out create mode 100644 modules/gdscript/tests/scripts/runtime/features/variadic_functions.gd create mode 100644 modules/gdscript/tests/scripts/runtime/features/variadic_functions.out diff --git a/core/doc_data.h b/core/doc_data.h index 50e67ec70c5..80b7fa2382b 100644 --- a/core/doc_data.h +++ b/core/doc_data.h @@ -107,6 +107,8 @@ public: bool is_experimental = false; String experimental_message; Vector arguments; + // NOTE: Only for GDScript for now. The rest argument is not saved to the XML file. + ArgumentDoc rest_argument; Vector errors_returned; String keywords; bool operator<(const MethodDoc &p_method) const { diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index f17eac9da9e..ef2e02b9ddf 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -314,12 +314,15 @@ List ConnectDialog::_filter_method_list(const List &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; @@ -609,10 +612,14 @@ String ConnectDialog::get_signature(const MethodInfo &p_method, PackedStringArra String arg_name = pi.name.is_empty() ? "arg" + itos(i) : pi.name; signature.append(arg_name + ": " + type_name); if (r_arg_names) { - r_arg_names->push_back(arg_name + ":" + type_name); + r_arg_names->push_back(arg_name + ": " + type_name); } } + if (p_method.flags & METHOD_FLAG_VARARG) { + signature.append(p_method.arguments.is_empty() ? "..." : ", ..."); + } + signature.append(")"); return String().join(signature); } @@ -1044,7 +1051,7 @@ void ConnectionsDock::_make_or_edit_connection() { } for (int i = 0; i < cd.binds.size(); i++) { - script_function_args.push_back("extra_arg_" + itos(i) + ":" + Variant::get_type_name(cd.binds[i].get_type())); + script_function_args.push_back("extra_arg_" + itos(i) + ": " + Variant::get_type_name(cd.binds[i].get_type())); } EditorNode::get_singleton()->emit_signal(SNAME("script_add_function_request"), target, cd.method, script_function_args); @@ -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(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) + ")"); } } diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 02e23526113..5512c572df5 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -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); diff --git a/editor/editor_help.h b/editor/editor_help.h index 3b3fbf4f7e2..8d4d091fbbe 100644 --- a/editor/editor_help.h +++ b/editor/editor_help.h @@ -302,6 +302,7 @@ class EditorHelpBit : public VBoxContainer { DocType doc_type; String value; Vector arguments; + ArgumentData rest_argument; String qualifiers; String resource_path; }; diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp index d1a0674248c..2cbdb4ad7b1 100644 --- a/editor/property_selector.cpp +++ b/editor/property_selector.cpp @@ -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"; } diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp index 053b1e25564..0b67ab42eb6 100644 --- a/modules/gdscript/editor/gdscript_docgen.cpp +++ b/modules/gdscript/editor/gdscript_docgen.cpp @@ -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") { diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 5d7d2f55ea4..69564324d12 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -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; } } diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 93471172dc3..48593a0f064 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -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 ¤t_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(); @@ -5853,6 +5894,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; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 74b04d36a8f..877a7e9c06e 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -2294,6 +2294,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++) { @@ -2309,6 +2310,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); } @@ -2475,6 +2481,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; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index fe4530572b8..1c507750f6e 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -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); diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index ce18062e9b1..e089b1b9c14 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -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; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 10928077599..2476d9962e5 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -456,6 +456,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; @@ -577,6 +578,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; } diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 1c17366fba6..1bdd1da42f2 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -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, diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 848d20d9e7d..b59ac7e5460 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -851,6 +851,7 @@ public: IdentifierNode *identifier = nullptr; Vector parameters; HashMap 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; } diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 71c6133bdcd..8caa4b0b8b6 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -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 '.'. diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index cc2f7fddf14..4a6e655805e 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -139,6 +139,7 @@ public: SEMICOLON, PERIOD, PERIOD_PERIOD, + PERIOD_PERIOD_PERIOD, COLON, DOLLAR, FORWARD_ARROW, diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 987694fc69f..2e7d9684d75 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -552,10 +552,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } else { if (p_argcount != _argument_count) { if (p_argcount > _argument_count) { - 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); + 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; @@ -566,21 +568,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)) { @@ -595,11 +597,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); @@ -610,16 +612,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 { @@ -2557,8 +2570,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; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index a35d5b87e24..4bffec872b2 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -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 function_nodes; diff --git a/modules/gdscript/tests/scripts/analyzer/errors/variadic_functions.gd b/modules/gdscript/tests/scripts/analyzer/errors/variadic_functions.gd new file mode 100644 index 00000000000..99b70fdb67c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/variadic_functions.gd @@ -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 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/variadic_functions.out b/modules/gdscript/tests/scripts/analyzer/errors/variadic_functions.out new file mode 100644 index 00000000000..acf67495ab4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/variadic_functions.out @@ -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". diff --git a/modules/gdscript/tests/scripts/parser/errors/variadic_func_params_after_rest.gd b/modules/gdscript/tests/scripts/parser/errors/variadic_func_params_after_rest.gd new file mode 100644 index 00000000000..ef8031e703e --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/variadic_func_params_after_rest.gd @@ -0,0 +1,5 @@ +func f(...args, extra_arg): + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/variadic_func_params_after_rest.out b/modules/gdscript/tests/scripts/parser/errors/variadic_func_params_after_rest.out new file mode 100644 index 00000000000..a7016e9ab25 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/variadic_func_params_after_rest.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Cannot have parameters after the rest parameter. diff --git a/modules/gdscript/tests/scripts/parser/errors/variadic_func_rest_after_rest.gd b/modules/gdscript/tests/scripts/parser/errors/variadic_func_rest_after_rest.gd new file mode 100644 index 00000000000..54641ffa96d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/variadic_func_rest_after_rest.gd @@ -0,0 +1,5 @@ +func f(...args, ...more_args): + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/variadic_func_rest_after_rest.out b/modules/gdscript/tests/scripts/parser/errors/variadic_func_rest_after_rest.out new file mode 100644 index 00000000000..a7016e9ab25 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/variadic_func_rest_after_rest.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Cannot have parameters after the rest parameter. diff --git a/modules/gdscript/tests/scripts/parser/errors/variadic_func_rest_with_default.gd b/modules/gdscript/tests/scripts/parser/errors/variadic_func_rest_with_default.gd new file mode 100644 index 00000000000..61ffe31361a --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/variadic_func_rest_with_default.gd @@ -0,0 +1,5 @@ +func f(...args = []): + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/variadic_func_rest_with_default.out b/modules/gdscript/tests/scripts/parser/errors/variadic_func_rest_with_default.out new file mode 100644 index 00000000000..a675ac8df56 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/variadic_func_rest_with_default.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +The rest parameter cannot have a default value. diff --git a/modules/gdscript/tests/scripts/parser/errors/variadic_func_static_init.gd b/modules/gdscript/tests/scripts/parser/errors/variadic_func_static_init.gd new file mode 100644 index 00000000000..80cc4429dc3 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/variadic_func_static_init.gd @@ -0,0 +1,5 @@ +static func _static_init(...args) -> void: + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/variadic_func_static_init.out b/modules/gdscript/tests/scripts/parser/errors/variadic_func_static_init.out new file mode 100644 index 00000000000..4bde990a5fd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/variadic_func_static_init.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Static constructor cannot have parameters. diff --git a/modules/gdscript/tests/scripts/runtime/features/abstract_methods.gd b/modules/gdscript/tests/scripts/runtime/features/abstract_methods.gd index 5ade2fe9903..2198d24549f 100644 --- a/modules/gdscript/tests/scripts/runtime/features/abstract_methods.gd +++ b/modules/gdscript/tests/scripts/runtime/features/abstract_methods.gd @@ -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 diff --git a/modules/gdscript/tests/scripts/runtime/features/variadic_functions.gd b/modules/gdscript/tests/scripts/runtime/features/variadic_functions.gd new file mode 100644 index 00000000000..91861849a52 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/variadic_functions.gd @@ -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) diff --git a/modules/gdscript/tests/scripts/runtime/features/variadic_functions.out b/modules/gdscript/tests/scripts/runtime/features/variadic_functions.out new file mode 100644 index 00000000000..b86bb84eb4e --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/variadic_functions.out @@ -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] diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd index e79005c398a..a106670c22e 100644 --- a/modules/gdscript/tests/scripts/utils.notest.gd +++ b/modules/gdscript/tests/scripts/utils.notest.gd @@ -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": diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp index fe838919e07..2c79f41ffe1 100644 --- a/modules/gdscript/tests/test_gdscript.cpp +++ b/modules/gdscript/tests/test_gdscript.cpp @@ -207,6 +207,10 @@ static void disassemble_function(const GDScriptFunction *p_func, const Vectoris_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