diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 0b7a10e485..74ae42376f 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- ILPP diagnostic errors should now be clickable in the editor to go to the source of the issue. (#4250) + ### Changed ### Deprecated diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs index 626e6492b1..d7d94b99b1 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs @@ -560,14 +560,191 @@ public static TypeReference GetEnumAsInt(this TypeReference typeReference) } } + /// + /// Finds the starting index of a method signature declaration + /// + /// The line of text to scan + /// The name of the method to search for + /// The starting index of the signature declaration if a declaration was found; otherwise -1 + private static int IndexOfSignature(string line, string name) + { + var column = line.IndexOf(name, StringComparison.Ordinal); + while (column >= 0) + { + // Skip any whitespace between the name and what follows it + var end = column + name.Length; + while (end < line.Length && char.IsWhiteSpace(line[end])) + { + end++; + } + if (end < line.Length && (line[end] == '(' || line[end] == '<')) + { + return column; + } + + // Check if there's a next match + column = line.IndexOf(name, column + 1, StringComparison.Ordinal); + } + return -1; + } + + /// + /// Finds the line of 's signature declaration. + /// + /// The sequence point, or null if the method has no debug information. + public static SequencePoint GetSignatureLocation(this MethodDefinition method) + { + // The SourceLocation is the first line of the method body. + var bodyPoint = method.GetSourceLocation(); + if (bodyPoint == null) + { + return method.DeclaringType.GetSourceLocation(); + } + + try + { + var lines = File.ReadAllLines(bodyPoint.Document.Url); + var startIndex = Math.Min(bodyPoint.StartLine, lines.Length) - 1; + // Work backwards from the first line in the method body + for (var i = startIndex; i >= 0; i--) + { + // If this line includes the method definition: FooBar( + var column = IndexOfSignature(lines[i], method.Name); + if (column < 0) + { + // A line above the body that ends a statement or block belongs to the previous member. + if (i < startIndex && (lines[i].Contains(';') || lines[i].Contains('}'))) + { + break; + } + continue; + } + + return new SequencePoint(method.Body.Instructions[0], bodyPoint.Document) + { + StartLine = i + 1, + EndLine = i + 1, + StartColumn = column + 1, + EndColumn = column + 1 + method.Name.Length, + }; + } + } + catch (Exception) + { + // We don't need to do anything on failure + } + // If we didn't find anything else, just use the first line of the method body that we found earlier + return bodyPoint; + } + + /// + /// Finds the first visible line of in the user's source, so a diagnostic or log can point at it. + /// + /// The sequence point, or null if the method has no debug information. + public static SequencePoint GetSourceLocation(this MethodDefinition method) + { + foreach (var sequencePoint in method.DebugInformation.SequencePoints) + { + if (sequencePoint.IsHidden) + { + continue; + } + return sequencePoint; + } + return null; + } + + /// + /// Finds the first visible line of any method in , so a diagnostic or log can at least open the right file. + /// + /// The sequence point, or null if the type has no debug information. + public static SequencePoint GetSourceLocation(this TypeDefinition type) + { + foreach (var method in type.Methods) + { + if (!method.HasBody) + { + continue; + } + + var sequencePoint = method.GetSourceLocation(); + if (sequencePoint != null) + { + return sequencePoint; + } + } + + return null; + } + + /// + /// Finds where is declared in the user's source. Fields carry no debug information of their own, + /// but a field initializer is compiled into the constructor with a sequence point on the declaration, so this looks for + /// the store to the field there. Falls back to when the field has no initializer. + /// + /// The sequence point, or null if the declaring type has no debug information. + public static SequencePoint GetSourceLocation(this FieldDefinition field) + { + var storeOpCode = field.IsStatic ? OpCodes.Stsfld : OpCodes.Stfld; + foreach (var method in field.DeclaringType.Methods) + { + if (!method.IsConstructor || method.IsStatic != field.IsStatic || !method.HasBody) + { + continue; + } + SequencePoint lastVisible = null; + foreach (var instruction in method.Body.Instructions) + { + // Store the last visible line as the line we're looking for might not be visible + // Once we find the line we're looking for we want to return the last visible line + var sequencePoint = method.DebugInformation.GetSequencePoint(instruction); + if (sequencePoint != null && !sequencePoint.IsHidden) + { + lastVisible = sequencePoint; + } + + // We're only looking for the store op codes on fields + var fieldReference = instruction.Operand as FieldReference; + if (instruction.OpCode != storeOpCode || fieldReference == null) + { + continue; + } + + // If this is the field we're looking for and we have a visible instruction, then we've found what we're looking for! + if (fieldReference.Resolve() == field && lastVisible != null) + { + return lastVisible; + } + } + } + + return field.DeclaringType.GetSourceLocation(); + } + + /// + /// Finds where is in the user's source. Called only when a diagnostic or log needs a location, + /// because finding a field's line scans its constructor's IL. + /// + /// The sequence point, or null if there is no debug information. + public static SequencePoint GetSourceLocation(this IMemberDefinition member) + { + return member switch + { + MethodDefinition method => method.GetSourceLocation(), + FieldDefinition field => field.GetSourceLocation(), + TypeDefinition type => type.GetSourceLocation(), + _ => null, + }; + } + public static void AddError(this List diagnostics, string message) { diagnostics.AddError((SequencePoint)null, message); } - public static void AddError(this List diagnostics, MethodDefinition methodDefinition, string message) + public static void AddError(this List diagnostics, IMemberDefinition member, string message) { - diagnostics.AddError(methodDefinition.DebugInformation.SequencePoints.FirstOrDefault(), message); + diagnostics.AddError(member?.GetSourceLocation(), message); } public static void AddError(this List diagnostics, SequencePoint sequencePoint, string message) @@ -587,9 +764,9 @@ public static void AddWarning(this List diagnostics, string m diagnostics.AddWarning((SequencePoint)null, message); } - public static void AddWarning(this List diagnostics, MethodDefinition methodDefinition, string message) + public static void AddWarning(this List diagnostics, IMemberDefinition member, string message) { - diagnostics.AddWarning(methodDefinition.DebugInformation.SequencePoints.FirstOrDefault(), message); + diagnostics.AddWarning(member?.GetSourceLocation(), message); } public static void AddWarning(this List diagnostics, SequencePoint sequencePoint, string message) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkSerializableILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkSerializableILPP.cs index c4123d5b88..b0af17b5b5 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkSerializableILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkSerializableILPP.cs @@ -80,11 +80,11 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) { if (type.HasInterface(CodeGenHelpers.INetworkSerializable_FullName)) { - m_Diagnostics.AddError($"{nameof(INetworkSerializeByMemcpy)} types may not implement {nameof(INetworkSerializable)} - choose one or the other."); + m_Diagnostics.AddError(type, $"{nameof(INetworkSerializeByMemcpy)} types may not implement {nameof(INetworkSerializable)} - choose one or the other."); } if (!type.IsValueType) { - m_Diagnostics.AddError($"{nameof(INetworkSerializeByMemcpy)} types must be unmanaged types."); + m_Diagnostics.AddError(type, $"{nameof(INetworkSerializeByMemcpy)} types must be unmanaged types."); } } } diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 5c9c0bddbb..07c9e5ae1b 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -32,37 +32,40 @@ public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiled private readonly List m_Diagnostics = new List(); - public void AddWrappedType(TypeReference wrappedType) + /// The type to register NetworkVariable serialization for. + /// The field, type or method in the user's code that uses the type, so diagnostics about it can be double-clicked. + /// The source line is only looked up when a diagnostic needs it. + public void AddWrappedType(TypeReference wrappedType, IMemberDefinition origin) { - if (!m_WrappedNetworkVariableTypes.Contains(wrappedType)) + if (!m_WrappedNetworkVariableTypes.ContainsKey(wrappedType)) { - m_WrappedNetworkVariableTypes.Add(wrappedType); + m_WrappedNetworkVariableTypes.Add(wrappedType, origin); var resolved = wrappedType.Resolve(); if (resolved != null) { if (resolved.FullName == "System.Collections.Generic.List`1") { - AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[0]); + AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[0], origin); } if (resolved.FullName == "System.Collections.Generic.HashSet`1") { - AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[0]); + AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[0], origin); } else if (resolved.FullName == "System.Collections.Generic.Dictionary`2") { - AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[0]); - AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[1]); + AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[0], origin); + AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[1], origin); } #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT else if (resolved.FullName == "Unity.Collections.NativeHashSet`1") { - AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[0]); + AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[0], origin); } else if (resolved.FullName == "Unity.Collections.NativeHashMap`2") { - AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[0]); - AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[1]); + AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[0], origin); + AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[1], origin); } #endif } @@ -125,7 +128,7 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) if (attribute.AttributeType.Name == nameof(GenerateSerializationForTypeAttribute)) { var wrappedType = mainModule.ImportReference((TypeReference)attribute.ConstructorArguments[0].Value); - AddWrappedType(wrappedType); + AddWrappedType(wrappedType, resolved); } } @@ -136,7 +139,7 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) if (attribute.AttributeType.Name == nameof(GenerateSerializationForTypeAttribute)) { var wrappedType = mainModule.ImportReference((TypeReference)attribute.ConstructorArguments[0].Value); - AddWrappedType(wrappedType); + AddWrappedType(wrappedType, method); } } } @@ -226,7 +229,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, var instructions = new List(); var processor = staticCtorMethodDef.Body.GetILProcessor(); - foreach (var type in m_WrappedNetworkVariableTypes) + foreach (var type in m_WrappedNetworkVariableTypes.Keys) { if (type.Resolve() == null) { @@ -243,7 +246,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, // for types that aren't in our official supported types list. GenericInstanceMethod serializeMethod = null; GenericInstanceMethod equalityMethod; - + var origin = m_WrappedNetworkVariableTypes[type]; if (type.Resolve().FullName == "Unity.Collections.NativeArray`1") { @@ -397,7 +400,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, if (!hasEmptyConstructor) { - m_Diagnostics.AddError($"{type} cannot be used in a network variable - Managed {nameof(INetworkSerializable)} instances must meet the `new()` (default empty constructor) constraint."); + m_Diagnostics.AddError(origin, $"{type} cannot be used in a network variable - Managed {nameof(INetworkSerializable)} instances must meet the `new()` (default empty constructor) constraint."); continue; } serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef); @@ -413,10 +416,10 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, { if (typeInterface.InterfaceType.Name.Contains(typeof(IEquatable<>).Name) && typeInterface.InterfaceType.IsGenericInstance) { - m_Diagnostics.AddError($"{type}: A generic IEquatable '{typeInterface.InterfaceType.FullName}' is not supported."); + m_Diagnostics.AddError(origin, $"{type}: A generic IEquatable '{typeInterface.InterfaceType.FullName}' is not supported."); } } - m_Diagnostics.AddError($"{type}: Managed type in NetworkVariable must implement IEquatable<{type}>."); + m_Diagnostics.AddError(origin, $"{type}: Managed type in NetworkVariable must implement IEquatable<{type}>."); equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef); } @@ -427,13 +430,13 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, // A single call violating its constraints makes CoreCLR reject this whole method, dropping every registration in the assembly. if (serializeMethod != null && !serializeMethod.SatisfiesGenericConstraints(out var serializeViolation)) { - m_Diagnostics.AddError($"{type} cannot be used in a network variable - {serializeViolation}."); + m_Diagnostics.AddError(origin, $"{type} cannot be used in a network variable - {serializeViolation}."); continue; } if (!equalityMethod.SatisfiesGenericConstraints(out var equalityViolation)) { - m_Diagnostics.AddError($"{type} cannot be used in a network variable - {equalityViolation}."); + m_Diagnostics.AddError(origin, $"{type} cannot be used in a network variable - {equalityViolation}."); continue; } @@ -447,7 +450,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, { foreach (var hiddenType in hiddenTypes) { - m_Diagnostics.AddError($"{type} cannot be used in a network variable - Cannot access the {hiddenType.Name} type to generate serialization as it is {(hiddenType.IsNestedPrivate ? "private" : "protected")}. To fix this, change the access modifier to internal or public."); + m_Diagnostics.AddError(origin, $"{type} cannot be used in a network variable - Cannot access the {hiddenType.Name} type to generate serialization as it is {(hiddenType.IsNestedPrivate ? "private" : "protected")}. To fix this, change the access modifier to internal or public."); } continue; } @@ -455,7 +458,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, // Log a warning in Mono so we can warn users who haven't yet switched their projects over. foreach (var hiddenType in hiddenTypes) { - m_Diagnostics.AddWarning($"NetworkVariable serialization for {type.Name} will fail in CoreCLR! With CoreCLR generated code cannot access the {hiddenType.Name} type to generate serialization as it is {(hiddenType.IsNestedPrivate ? "private" : "protected")}. To fix this, change the access modifier to internal or public."); + m_Diagnostics.AddWarning(origin, $"NetworkVariable serialization for {type.Name} will fail in CoreCLR! With CoreCLR generated code cannot access the {hiddenType.Name} type to generate serialization as it is {(hiddenType.IsNestedPrivate ? "private" : "protected")}. To fix this, change the access modifier to internal or public."); } } @@ -582,7 +585,11 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, private readonly Dictionary m_FastBufferReader_ReadValue_MethodRefs = new Dictionary(); private readonly List m_FastBufferReader_ExtensionMethodRefs = new List(); - private HashSet m_WrappedNetworkVariableTypes = new HashSet(); + /// + /// The types to register NetworkVariable serialization for, each mapped to where the user's code first uses it, + /// so diagnostics about the type can point there. See . + /// + private Dictionary m_WrappedNetworkVariableTypes = new Dictionary(); internal static readonly Type[] BaseSupportedTypes = new[] { @@ -1290,10 +1297,17 @@ private bool ImportReferences(ModuleDefinition moduleDefinition, string[] assemb return true; } + private struct FieldRef + { + public TypeReference Type; + public FieldDefinition Origin; + } + // This gets all fields from this type as well as any parent types, up to (but not including) the base NetworkBehaviour class // Importantly... this also resolves any generics, so if the base class is Foo and contains a field of NetworkVariable, // and this class is Bar : Foo, it will properly resolve NetworkVariable to NetworkVariable. - private void GetAllFieldsAndResolveGenerics(TypeDefinition type, ref List fieldTypes, Dictionary genericParameters = null) + // Each type is paired with the field it came from, so diagnostics about the type can point at the field's declaration. + private void GetAllFieldsAndResolveGenerics(TypeDefinition type, ref List fieldTypes, Dictionary genericParameters = null) { foreach (var field in type.Fields) { @@ -1314,11 +1328,11 @@ private void GetAllFieldsAndResolveGenerics(TypeDefinition type, ref List(); + var fieldTypes = new List(); GetAllFieldsAndResolveGenerics(typeDefinition, ref fieldTypes); - foreach (var type in fieldTypes) + foreach (var fieldReference in fieldTypes) { - //var type = field.FieldType; + var type = fieldReference.Type; + var origin = fieldReference.Origin.DeclaringType == typeDefinition ? (IMemberDefinition)fieldReference.Origin : typeDefinition; if (type.IsGenericInstance) { foreach (var attribute in type.Resolve().CustomAttributes) @@ -1452,11 +1467,11 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass var genericInstanceType = (GenericInstanceType)type; if (idx < 0 || idx >= genericInstanceType.GenericArguments.Count) { - m_Diagnostics.AddError($"{type} has a {nameof(GenerateSerializationForGenericParameterAttribute)} referencing a parameter index outside the valid range (0-{genericInstanceType.GenericArguments.Count - 1}"); + m_Diagnostics.AddError(origin, $"{type} has a {nameof(GenerateSerializationForGenericParameterAttribute)} referencing a parameter index outside the valid range (0-{genericInstanceType.GenericArguments.Count - 1}"); continue; } var wrappedType = genericInstanceType.GenericArguments[idx]; - AddWrappedType(wrappedType); + AddWrappedType(wrappedType, origin); } } } @@ -1485,11 +1500,11 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass var genericInstanceType = (GenericInstanceType)baseType; if (idx < 0 || idx >= genericInstanceType.GenericArguments.Count) { - m_Diagnostics.AddError($"{baseType} has a {nameof(GenerateSerializationForGenericParameterAttribute)} referencing a parameter index outside the valid range (0-{genericInstanceType.GenericArguments.Count - 1}"); + m_Diagnostics.AddError(origin, $"{baseType} has a {nameof(GenerateSerializationForGenericParameterAttribute)} referencing a parameter index outside the valid range (0-{genericInstanceType.GenericArguments.Count - 1}"); continue; } var wrappedType = genericInstanceType.GenericArguments[idx]; - AddWrappedType(wrappedType); + AddWrappedType(wrappedType, origin); } } } @@ -1626,6 +1641,18 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass private CustomAttribute CheckAndGetRpcAttribute(MethodDefinition methodDefinition) { CustomAttribute rpcAttribute = null; + var foundSignature = false; + SequencePoint signaturePoint = null; + + SequencePoint Signature() + { + if (!foundSignature) + { + signaturePoint = methodDefinition.GetSignatureLocation(); + foundSignature = true; + } + return signaturePoint; + } foreach (var customAttribute in methodDefinition.CustomAttributes) { @@ -1639,51 +1666,51 @@ private CustomAttribute CheckAndGetRpcAttribute(MethodDefinition methodDefinitio if (methodDefinition.IsStatic) { - m_Diagnostics.AddError(methodDefinition, "RPC method must not be static!"); + m_Diagnostics.AddError(Signature(), "RPC method must not be static!"); isValid = false; } if (methodDefinition.IsAbstract) { - m_Diagnostics.AddError(methodDefinition, "RPC method must not be abstract!"); + m_Diagnostics.AddError(Signature(), "RPC method must not be abstract!"); isValid = false; } if (methodDefinition.HasGenericParameters) { - m_Diagnostics.AddError(methodDefinition, "RPC method must not be generic!"); + m_Diagnostics.AddError(Signature(), "RPC method must not be generic!"); isValid = false; } if (methodDefinition.ReturnType != methodDefinition.Module.TypeSystem.Void) { - m_Diagnostics.AddError(methodDefinition, "RPC method must return `void`!"); + m_Diagnostics.AddError(Signature(), "RPC method must return `void`!"); isValid = false; } if (customAttributeType_FullName == CodeGenHelpers.ServerRpcAttribute_FullName && !methodDefinition.Name.EndsWith("ServerRpc", StringComparison.OrdinalIgnoreCase)) { - m_Diagnostics.AddError(methodDefinition, "ServerRpc method must end with 'ServerRpc' suffix!"); + m_Diagnostics.AddError(Signature(), "ServerRpc method must end with 'ServerRpc' suffix!"); isValid = false; } if (customAttributeType_FullName == CodeGenHelpers.ClientRpcAttribute_FullName && !methodDefinition.Name.EndsWith("ClientRpc", StringComparison.OrdinalIgnoreCase)) { - m_Diagnostics.AddError(methodDefinition, "ClientRpc method must end with 'ClientRpc' suffix!"); + m_Diagnostics.AddError(Signature(), "ClientRpc method must end with 'ClientRpc' suffix!"); isValid = false; } if (customAttributeType_FullName == CodeGenHelpers.RpcAttribute_FullName && !methodDefinition.Name.EndsWith("Rpc", StringComparison.OrdinalIgnoreCase)) { - m_Diagnostics.AddError(methodDefinition, "Rpc method must end with 'Rpc' suffix!"); + m_Diagnostics.AddError(Signature(), "Rpc method must end with 'Rpc' suffix!"); // Extra compiler information if a method was defined as a local function if (methodDefinition.Name.Contains("Rpc|", StringComparison.OrdinalIgnoreCase) && methodDefinition.Name.StartsWith("g__", StringComparison.OrdinalIgnoreCase)) { - m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.Name} appears to be a local function. Local functions cannot be RPCs."); + m_Diagnostics.AddError(Signature(), $"{methodDefinition.Name} appears to be a local function. Local functions cannot be RPCs."); } isValid = false; @@ -1704,15 +1731,15 @@ private CustomAttribute CheckAndGetRpcAttribute(MethodDefinition methodDefinitio { if (methodDefinition.Name.EndsWith("ServerRpc", StringComparison.OrdinalIgnoreCase)) { - m_Diagnostics.AddError(methodDefinition, $"ServerRpc method {methodDefinition} must be marked with 'ServerRpc' attribute!"); + m_Diagnostics.AddError(Signature(), $"ServerRpc method {methodDefinition.Name} must be marked with 'ServerRpc' attribute!"); } else if (methodDefinition.Name.EndsWith("ClientRpc", StringComparison.OrdinalIgnoreCase)) { - m_Diagnostics.AddError(methodDefinition, $"ClientRpc method {methodDefinition} must be marked with 'ClientRpc' attribute!"); + m_Diagnostics.AddError(Signature(), $"ClientRpc method {methodDefinition.Name} must be marked with 'ClientRpc' attribute!"); } else if (methodDefinition.Name.EndsWith("ExtRpc", StringComparison.OrdinalIgnoreCase)) { - m_Diagnostics.AddError(methodDefinition, $"Ext Rpc method {methodDefinition} must be marked with 'ExtRpc' attribute!"); + m_Diagnostics.AddError(Signature(), $"Ext Rpc method {methodDefinition.Name} must be marked with 'ExtRpc' attribute!"); } return null; @@ -2216,7 +2243,7 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA { if (!hasRpcParams) { - m_Diagnostics.AddError($"{methodDefinition}: {nameof(SendTo)}.{nameof(SendTo.SpecifiedInParams)} cannot be used without a final parameter of type {CodeGenHelpers.RpcParams_FullName}."); + m_Diagnostics.AddError(methodDefinition.GetSignatureLocation(), $"{methodDefinition.Name}: {nameof(SendTo)}.{nameof(SendTo.SpecifiedInParams)} cannot be used without a final parameter of type {CodeGenHelpers.RpcParams_FullName}."); } foreach (var attrField in rpcAttribute.Fields) @@ -2224,7 +2251,7 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA switch (attrField.Name) { case nameof(RpcAttribute.AllowTargetOverride): - m_Diagnostics.AddWarning($"{methodDefinition}: {nameof(RpcAttribute.AllowTargetOverride)} is ignored with {nameof(SendTo)}.{nameof(SendTo.SpecifiedInParams)}"); + m_Diagnostics.AddWarning(methodDefinition.GetSignatureLocation(), $"{methodDefinition.Name}: {nameof(RpcAttribute.AllowTargetOverride)} is ignored with {nameof(SendTo)}.{nameof(SendTo.SpecifiedInParams)}"); break; } } @@ -2233,7 +2260,7 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA { if (!hasRpcParams) { - m_Diagnostics.AddError($"{methodDefinition}: {nameof(RpcAttribute.AllowTargetOverride)} cannot be used without a final parameter of type {CodeGenHelpers.RpcParams_FullName}."); + m_Diagnostics.AddError(methodDefinition.GetSignatureLocation(), $"{methodDefinition.Name}: {nameof(RpcAttribute.AllowTargetOverride)} cannot be used without a final parameter of type {CodeGenHelpers.RpcParams_FullName}."); } } @@ -2449,7 +2476,7 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA } else { - m_Diagnostics.AddError("Unsupported attribute parameter type."); + m_Diagnostics.AddError(methodDefinition.GetSignatureLocation(), "Unsupported attribute parameter type."); } } @@ -2461,7 +2488,7 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA if (!found) { - m_Diagnostics.AddError($"{nameof(RpcAttribute)} contains field {field} which is not present in {nameof(RpcAttribute.RpcAttributeParams)}."); + m_Diagnostics.AddError(methodDefinition.GetSignatureLocation(), $"{nameof(RpcAttribute)} contains field {field} which is not present in {nameof(RpcAttribute.RpcAttributeParams)}."); } } @@ -2486,14 +2513,14 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA if (paramType.FullName == CodeGenHelpers.ClientRpcSendParams_FullName || paramType.FullName == CodeGenHelpers.ClientRpcReceiveParams_FullName) { - m_Diagnostics.AddError($"Rpcs may not accept {paramType.FullName} as a parameter. Use {nameof(ClientRpcParams)} instead."); + m_Diagnostics.AddError(methodDefinition.GetSignatureLocation(), $"Rpcs may not accept {paramType.FullName} as a parameter. Use {nameof(ClientRpcParams)} instead."); continue; } if (paramType.FullName == CodeGenHelpers.ServerRpcSendParams_FullName || paramType.FullName == CodeGenHelpers.ServerRpcReceiveParams_FullName) { - m_Diagnostics.AddError($"Rpcs may not accept {paramType.FullName} as a parameter. Use {nameof(ServerRpcParams)} instead."); + m_Diagnostics.AddError(methodDefinition.GetSignatureLocation(), $"Rpcs may not accept {paramType.FullName} as a parameter. Use {nameof(ServerRpcParams)} instead."); continue; } // ServerRpcParams @@ -2501,11 +2528,11 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA { if (paramIndex != paramCount - 1) { - m_Diagnostics.AddError(methodDefinition, $"{nameof(ServerRpcParams)} must be the last parameter in a ServerRpc."); + m_Diagnostics.AddError(methodDefinition.GetSignatureLocation(), $"{nameof(ServerRpcParams)} must be the last parameter in a ServerRpc."); } if (!isServerRpc) { - m_Diagnostics.AddError($"Only ServerRpcs may accept {nameof(ServerRpcParams)} as a parameter."); + m_Diagnostics.AddError(methodDefinition.GetSignatureLocation(), $"Only ServerRpcs may accept {nameof(ServerRpcParams)} as a parameter."); } continue; } @@ -2514,11 +2541,11 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA { if (paramIndex != paramCount - 1) { - m_Diagnostics.AddError(methodDefinition, $"{nameof(ClientRpcParams)} must be the last parameter in a ClientRpc."); + m_Diagnostics.AddError(methodDefinition.GetSignatureLocation(), $"{nameof(ClientRpcParams)} must be the last parameter in a ClientRpc."); } if (!isClientRpc) { - m_Diagnostics.AddError($"Only clientRpcs may accept {nameof(ClientRpcParams)} as a parameter."); + m_Diagnostics.AddError(methodDefinition.GetSignatureLocation(), $"Only clientRpcs may accept {nameof(ClientRpcParams)} as a parameter."); } continue; } @@ -2527,11 +2554,11 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA { if (paramIndex != paramCount - 1) { - m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.Name} is invalid. {nameof(RpcParams)} must be the last parameter in a ClientRpc."); + m_Diagnostics.AddError(methodDefinition.GetSignatureLocation(), $"{methodDefinition.Name} is invalid. {nameof(RpcParams)} must be the last parameter in a ClientRpc."); } if (!isGenericRpc) { - m_Diagnostics.AddError($"{methodDefinition.Name} is invalid. Only Rpcs may accept {nameof(RpcParams)} as a parameter."); + m_Diagnostics.AddError(methodDefinition.GetSignatureLocation(), $"{methodDefinition.Name} is invalid. Only Rpcs may accept {nameof(RpcParams)} as a parameter."); } continue; }