Skip to content

Commit

Permalink
GDScript: Add support for variadic functions
Browse files Browse the repository at this point in the history
  • Loading branch information
dalexeev committed Dec 4, 2024
1 parent 1f47e4c commit 545030d
Show file tree
Hide file tree
Showing 32 changed files with 379 additions and 46 deletions.
2 changes: 2 additions & 0 deletions core/doc_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ class DocData {
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 {
Expand Down
36 changes: 23 additions & 13 deletions editor/connections_dialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -307,13 +307,16 @@ List<MethodInfo> ConnectDialog::_filter_method_list(const List<MethodInfo> &p_me
}

if (check_signal) {
if (mi.arguments.size() != effective_args.size()) {
const int min_argc = mi.arguments.size() - mi.default_arguments.size();
const int max_argc = (mi.flags & METHOD_FLAG_VARARG) ? INT_MAX : mi.arguments.size();

if (effective_args.size() < min_argc || effective_args.size() > max_argc) {
continue;
}

bool type_mismatch = false;
const List<Pair<Variant::Type, StringName>>::Element *E = effective_args.front();
for (const List<PropertyInfo>::Element *F = mi.arguments.front(); F; F = F->next(), E = E->next()) {
for (const List<PropertyInfo>::Element *F = mi.arguments.front(); F && E; F = F->next(), E = E->next()) {
Variant::Type stype = E->get().first;
Variant::Type mtype = F->get().type;

Expand Down Expand Up @@ -603,10 +606,17 @@ 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) {
if (!p_method.arguments.is_empty()) {
signature.append(", ");
}
signature.append("...");
}

signature.append(")");
return String().join(signature);
}
Expand Down Expand Up @@ -989,7 +999,7 @@ void ConnectionsDock::_make_or_edit_connection() {
PackedStringArray script_function_args = connect_dialog->get_signal_args();
script_function_args.resize(script_function_args.size() - cd.unbinds);
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);
Expand Down Expand Up @@ -1127,9 +1137,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()) {
Expand All @@ -1138,12 +1148,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) + ")");
}

/*
Expand All @@ -1160,12 +1170,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) + ")");
}
}

Expand Down
47 changes: 45 additions & 2 deletions editor/editor_help.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,15 @@ 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->add_text(rest_argument.name.is_empty() ? "args" : rest_argument.name);
class_desc->add_text(": ");
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);
Expand Down Expand Up @@ -2030,6 +2039,15 @@ 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->add_text(rest_argument.name.is_empty() ? "args" : rest_argument.name);
class_desc->add_text(": ");
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);
Expand Down Expand Up @@ -3333,9 +3351,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 });
}
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 };
current.is_vararg = method.qualifiers.contains("vararg");

if (method.name == p_method_name) {
result = current;
Expand Down Expand Up @@ -3533,6 +3555,27 @@ void EditorHelpBit::_update_labels() {
}
}

if (help_data.is_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->add_text(rest_argument.name.is_empty() ? "args" : rest_argument.name);
title->add_text(": ");
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);
title->add_text(")");
title->pop(); // color
Expand Down
2 changes: 2 additions & 0 deletions editor/editor_help.h
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ class EditorHelpBit : public VBoxContainer {
String experimental_message;
DocType doc_type; // For method return type.
Vector<ArgumentData> arguments; // For methods and signals.
ArgumentData rest_argument; // For methods.
bool is_vararg = false; // For methods (native methods have no named rest argument).
};

inline static HashMap<StringName, HelpData> doc_class_cache;
Expand Down
11 changes: 11 additions & 0 deletions editor/property_selector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,19 @@ void PropertySelector::_update_search() {
}
}

if (mi.flags & METHOD_FLAG_VARARG) {
if (!mi.arguments.is_empty()) {
desc += ", ";
}
desc += "...";
}

desc += ")";

if (mi.flags & METHOD_FLAG_VARARG) {
desc += " vararg";
}

if (mi.flags & METHOD_FLAG_CONST) {
desc += " const";
}
Expand Down
15 changes: 14 additions & 1 deletion modules/gdscript/editor/gdscript_docgen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,20 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
method_doc.deprecated_message = m_func->doc_data.deprecated_message;
method_doc.is_experimental = m_func->doc_data.is_experimental;
method_doc.experimental_message = m_func->doc_data.experimental_message;
method_doc.qualifiers = m_func->is_static ? "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_static) {
if (!method_doc.qualifiers.is_empty()) {
method_doc.qualifiers += " ";
}
method_doc.qualifiers += "static";
}

if (func_name == "_init") {
method_doc.return_type = "void";
Expand Down
2 changes: 1 addition & 1 deletion modules/gdscript/editor/gdscript_highlighter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,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;
}
}
Expand Down
52 changes: 48 additions & 4 deletions modules/gdscript/gdscript_analyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1749,6 +1749,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("_")) {
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();
Expand Down Expand Up @@ -1815,15 +1842,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`.
Expand Down Expand Up @@ -1853,6 +1888,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();
Expand Down Expand Up @@ -5739,6 +5780,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;
Expand Down
10 changes: 10 additions & 0 deletions modules/gdscript/gdscript_compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2295,6 +2295,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++) {
Expand All @@ -2310,6 +2311,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);
}

Expand Down Expand Up @@ -2477,6 +2483,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;
Expand Down
Loading

0 comments on commit 545030d

Please sign in to comment.