diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 7e29a9c0fe7..2c5e6d46e76 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -3245,6 +3245,26 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call); } } + +#ifdef DEBUG_ENABLED + // Consider `Signal(self, "my_signal")` as an implicit use of the signal. + if (builtin_type == Variant::SIGNAL && p_call->arguments.size() >= 2) { + const GDScriptParser::ExpressionNode *object_arg = p_call->arguments[0]; + if (object_arg && object_arg->type == GDScriptParser::Node::SELF) { + const GDScriptParser::ExpressionNode *signal_arg = p_call->arguments[1]; + if (signal_arg && signal_arg->is_constant) { + const StringName &signal_name = signal_arg->reduced_value; + if (parser->current_class->has_member(signal_name)) { + const GDScriptParser::ClassNode::Member &member = parser->current_class->get_member(signal_name); + if (member.type == GDScriptParser::ClassNode::Member::SIGNAL) { + member.signal->usages++; + } + } + } + } + } +#endif + p_call->set_datatype(call_type); return; } else if (GDScriptUtilityFunctions::function_exists(function_name)) { @@ -3479,6 +3499,20 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, caller_type); } + + // Consider `emit_signal()`, `connect()`, and `disconnect()` as implicit uses of the signal. + if (is_self && (p_call->function_name == SNAME("emit_signal") || p_call->function_name == SNAME("connect") || p_call->function_name == SNAME("disconnect")) && !p_call->arguments.is_empty()) { + const GDScriptParser::ExpressionNode *signal_arg = p_call->arguments[0]; + if (signal_arg && signal_arg->is_constant) { + const StringName &signal_name = signal_arg->reduced_value; + if (parser->current_class->has_member(signal_name)) { + const GDScriptParser::ClassNode::Member &member = parser->current_class->get_member(signal_name); + if (member.type == GDScriptParser::ClassNode::Member::SIGNAL) { + member.signal->usages++; + } + } + } + } #endif // DEBUG_ENABLED call_type = return_type; diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd index d937dfdcfe4..37f118dc5d6 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd @@ -1,12 +1,29 @@ -signal s1() -signal s2() -signal s3() +# Doesn't produce the warning: +signal used_as_first_class_signal() +signal used_with_signal_constructor() +signal used_with_signal_emit() +signal used_with_object_emit_signal() +signal used_with_object_connect() +signal used_with_object_disconnect() +signal used_with_self_prefix() + +# Produce the warning: +signal used_with_dynamic_name() +signal just_unused() @warning_ignore("unused_signal") -signal s4() +signal unused_but_ignored() func no_exec(): - s1.emit() - print(s2) + print(used_as_first_class_signal) + print(Signal(self, "used_with_signal_constructor")) + used_with_signal_emit.emit() + print(emit_signal("used_with_object_emit_signal")) + print(connect("used_with_object_connect", Callable())) + disconnect("used_with_object_disconnect", Callable()) + print(self.emit_signal("used_with_self_prefix")) + + var dynamic_name := "used_with_dynamic_name" + print(emit_signal(dynamic_name)) func test(): pass diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out index ff570178302..39ddf91c767 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out @@ -1,5 +1,9 @@ GDTEST_OK >> WARNING ->> Line: 3 +>> Line: 11 >> UNUSED_SIGNAL ->> The signal "s3" is declared but never explicitly used in the class. +>> The signal "used_with_dynamic_name" is declared but never explicitly used in the class. +>> WARNING +>> Line: 12 +>> UNUSED_SIGNAL +>> The signal "just_unused" is declared but never explicitly used in the class.