From 6aece5a76981069c64a3ee1bd0ed49af5e7cfb96 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Thu, 19 Feb 2026 18:56:37 -0500 Subject: [PATCH] Handle generic types in proto message name generation Generic types like Translation now produce qualified message names (e.g. TranslationOfFaqTranslationQueryItem) to avoid duplicate message definitions in generated .proto files. Co-Authored-By: Claude Opus 4.6 --- .../ProtoFileGenerator.cs | 12 ++++++++---- Svrnty.CQRS.Grpc.Generators/ProtoTypeMapper.cs | 17 ++++++++++++++++- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Svrnty.CQRS.Grpc.Generators/ProtoFileGenerator.cs b/Svrnty.CQRS.Grpc.Generators/ProtoFileGenerator.cs index 100bc3a..d96273d 100644 --- a/Svrnty.CQRS.Grpc.Generators/ProtoFileGenerator.cs +++ b/Svrnty.CQRS.Grpc.Generators/ProtoFileGenerator.cs @@ -413,17 +413,21 @@ internal class ProtoFileGenerator private void GenerateComplexTypeMessage(INamedTypeSymbol? type) { - if (type == null || _generatedMessages.Contains(type.Name)) + if (type == null) + return; + + var messageName = ProtoFileTypeMapper.GetProtoMessageName(type); + if (_generatedMessages.Contains(messageName)) return; // Don't generate messages for system types or primitives if (type.ContainingNamespace?.ToString().StartsWith("System") == true) return; - _generatedMessages.Add(type.Name); + _generatedMessages.Add(messageName); - _messagesBuilder.AppendLine($"// {type.Name} entity"); - _messagesBuilder.AppendLine($"message {type.Name} {{"); + _messagesBuilder.AppendLine($"// {messageName} entity"); + _messagesBuilder.AppendLine($"message {messageName} {{"); // Collect nested complex types to generate after closing this message var nestedComplexTypes = new List(); diff --git a/Svrnty.CQRS.Grpc.Generators/ProtoTypeMapper.cs b/Svrnty.CQRS.Grpc.Generators/ProtoTypeMapper.cs index d4a7f58..9e7b237 100644 --- a/Svrnty.CQRS.Grpc.Generators/ProtoTypeMapper.cs +++ b/Svrnty.CQRS.Grpc.Generators/ProtoTypeMapper.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Microsoft.CodeAnalysis; namespace Svrnty.CQRS.Grpc.Generators; @@ -151,13 +152,27 @@ internal static class ProtoFileTypeMapper // Complex types (classes/records) become message types if (typeSymbol.TypeKind == TypeKind.Class || typeSymbol.TypeKind == TypeKind.Struct) { - return typeName; // Reference the message type by name + return GetProtoMessageName(typeSymbol); // Reference the message type by name (handles generics) } // Fallback return "string"; // Default to string for unknown types } + /// + /// Gets the proto message name for a type, handling generic types by qualifying + /// with type arguments. e.g. Translation<FaqTranslationQueryItem> becomes TranslationOfFaqTranslationQueryItem. + /// + public static string GetProtoMessageName(ITypeSymbol typeSymbol) + { + if (typeSymbol is INamedTypeSymbol namedType && namedType.IsGenericType && namedType.TypeArguments.Length > 0) + { + var typeArgs = string.Join("And", namedType.TypeArguments.Select(t => GetProtoMessageName(t))); + return $"{namedType.Name}Of{typeArgs}"; + } + return typeSymbol.Name; + } + /// /// Converts C# PascalCase property name to proto snake_case field name. /// Uses simple conversion: add underscore before each uppercase letter (except first).