Ver Fonte

Merge pull request #89675 from dalexeev/gds-correct-unused-signal-warning

GDScript: Do not produce `UNUSED_SIGNAL` warning for common implicit uses
Rémi Verschelde há 1 ano atrás
pai
commit
d3be91e6ab

+ 34 - 0
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;

+ 23 - 6
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

+ 6 - 2
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.