From 64d4b36789bbaab385484c25af70ec3d42492c08 Mon Sep 17 00:00:00 2001 From: Hank Patton Date: Wed, 19 Aug 2026 11:35:29 -0500 Subject: [PATCH] Fix duplicate-signature false positives on calls at member depth The signature regex matches call sites as readily as declarations, and `(\S+)` will capture punctuation as a "return type". That is normally harmless because matches are non-overlapping: an ordinary declaration consumes its own opening brace, so a call inside the body has no preceding token left to match against. Any call sitting at class-member depth escapes that, because there is no enclosing declaration match to consume the token in front of it: - field initializers -- `static bool X = ReadPref();` captures `=`; - expression-bodied members -- `int Total => Count();` captures `=>`, and `Current => _current ??= Build();` captures `??=`; - the first statement of a body whose declaration the regex could not match at all, i.e. an explicit interface implementation, since `(\w+)` cannot span the dot in `IThing.Notify` -- the body's opening brace is then captured instead. All of these sit at class-member depth, so the brace-depth guard added in "Prevent validator false positives from method-body calls" cannot reject them, and valid code is reported as a duplicate. Zero-argument methods are what bite in practice, since the key includes parameter types and an empty list always matches. This repository's own sources contain four instances (McpLog, StdioBridgeHost, BridgeControlService, SecureKeyStore). Rather than a third targeted skip after this and the `new` constructor case, reject the class: a captured return type that is punctuation is never a declaration. `(` is kept so tuple returns still validate. --- MCPForUnity/Editor/Tools/ManageScript.cs | 8 +++ .../Tools/ManageScriptValidationTests.cs | 49 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/MCPForUnity/Editor/Tools/ManageScript.cs b/MCPForUnity/Editor/Tools/ManageScript.cs index 4f94df606..8f9c64ec8 100644 --- a/MCPForUnity/Editor/Tools/ManageScript.cs +++ b/MCPForUnity/Editor/Tools/ManageScript.cs @@ -2758,6 +2758,14 @@ private static void CheckDuplicateMethodSignatures(string contents, System.Colle string returnType = sm.Groups[1].Value; string methodName = sm.Groups[2].Value; if (string.Equals(returnType, "new", StringComparison.Ordinal)) continue; // constructor invocation, not a method declaration + // A punctuation "return type" means this match is a CALL, not a declaration: + // the opening brace of a method body ("{ Foo();") or an expression-bodied + // member ("=> Foo();"). Both sit at class-member depth, so the brace-depth + // guard below cannot reject them. + if (returnType.Length == 0) continue; + char returnTypeStart = returnType[0]; + if (!char.IsLetter(returnTypeStart) && returnTypeStart != '_' + && returnTypeStart != '@' && returnTypeStart != '(') continue; if (IsCSharpKeyword(methodName)) continue; int paramCount = CountTopLevelParams(sm.Groups[3].Value); string paramTypes = ExtractParamTypes(sm.Groups[3].Value); diff --git a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptValidationTests.cs b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptValidationTests.cs index 0b1aa7d0a..1cc527e8c 100644 --- a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptValidationTests.cs +++ b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptValidationTests.cs @@ -227,6 +227,55 @@ public void Process(string x) { } "Overloads with different param types but same count should not be flagged"); } + [Test] + public void DuplicateDetection_ExplicitInterfaceImplementation_NotFlagged() + { + string code = @"using UnityEngine; +public interface IThing { void Notify(); } +public class Foo : MonoBehaviour, IThing +{ + void IThing.Notify() + { + Ping(); + } + + private void Ping() { } +}"; + var errors = CallValidateScriptSyntaxUnity(code); + Assert.IsFalse(HasDuplicateMethodError(errors), + "A call as the first statement of an explicit interface implementation is not a declaration"); + } + + [Test] + public void DuplicateDetection_ExpressionBodiedCall_NotFlagged() + { + string code = @"using UnityEngine; +public class Foo : MonoBehaviour +{ + public int Total => Count(); + + private int Count() => 0; +}"; + var errors = CallValidateScriptSyntaxUnity(code); + Assert.IsFalse(HasDuplicateMethodError(errors), + "A call inside an expression-bodied member is not a declaration"); + } + + [Test] + public void DuplicateDetection_FieldInitializerCall_NotFlagged() + { + string code = @"using UnityEngine; +public class Foo : MonoBehaviour +{ + private static bool _enabled = ReadPref(); + + private static bool ReadPref() { return false; } +}"; + var errors = CallValidateScriptSyntaxUnity(code); + Assert.IsFalse(HasDuplicateMethodError(errors), + "A call in a field initializer is not a declaration"); + } + // --- Duplicate method detection: true positive tests --- [Test]