diff --git a/Rdmp.Core.Generators/TypeRegistryGenerator.cs b/Rdmp.Core.Generators/TypeRegistryGenerator.cs index 60aa9e2cb..7056b4f43 100644 --- a/Rdmp.Core.Generators/TypeRegistryGenerator.cs +++ b/Rdmp.Core.Generators/TypeRegistryGenerator.cs @@ -70,7 +70,8 @@ private static void CollectTypes(IAssemblySymbol assembly, List types, name.StartsWith("NUnit") || name.StartsWith("coverlet") || name.StartsWith("NSubstitute") || - name == "CommandLine") + name == "CommandLine" || + name == "Spectre.Console.Ansi") return; CollectTypesFromNamespace(assembly.GlobalNamespace, types, includeInternal); @@ -146,6 +147,13 @@ private static bool ShouldIncludeType(INamedTypeSymbol type, bool includeInterna private static string GenerateTypeRegistry(List types) { + // Deduplicate types by qualified name (can happen when multiple assemblies expose the same type). + // Normalize by stripping global:: so dedup matches the dictionary key space. + var deduped = types + .GroupBy(t => t.FullName.Replace("global::", "")) + .Select(g => g.First()) + .ToList(); + var sb = new StringBuilder(); sb.AppendLine("// "); @@ -162,7 +170,7 @@ private static string GenerateTypeRegistry(List types) sb.AppendLine(); sb.AppendLine("/// "); sb.AppendLine("/// Compile-time generated type registry containing all types from referenced assemblies."); - sb.AppendLine($"/// Generated at compile-time with {types.Count} types."); + sb.AppendLine($"/// Generated at compile-time with {deduped.Count} types."); sb.AppendLine("/// "); sb.AppendLine("public static partial class CompiledTypeRegistry"); sb.AppendLine("{"); @@ -173,14 +181,14 @@ private static string GenerateTypeRegistry(List types) sb.AppendLine(" var dict = new Dictionary(StringComparer.OrdinalIgnoreCase);"); sb.AppendLine(); - // Group types by short name to handle duplicates - var grouped = types.GroupBy(t => t.ShortName); - - foreach (var group in grouped) + // Group types by short name to handle duplicates. + // Materialize each group into a list to avoid repeated enumeration. + foreach (var group in deduped.GroupBy(t => t.ShortName)) { - if (group.Count() == 1) + var members = group.ToList(); + if (members.Count == 1) { - var type = group.First(); + var type = members[0]; sb.AppendLine($" // {type.FullName}"); // Add all lookup variants AddTypeLookup(sb, type.ShortName, type.FullName); @@ -189,7 +197,7 @@ private static string GenerateTypeRegistry(List types) else { // Multiple types with same short name - only store full names - foreach (var type in group) + foreach (var type in members) { sb.AppendLine($" // {type.FullName} (short name conflict)"); AddTypeLookup(sb, type.FullName, type.FullName); @@ -219,11 +227,10 @@ private static string GenerateTypeRegistry(List types) private static void AddTypeLookup(StringBuilder sb, string key, string typeFullName) { - // Escape quotes in key - var escapedKey = key.Replace("\"", "\\\""); - // Remove global:: prefix for the key lookup, but keep it for typeof() - var lookupKey = key.Replace("global::", ""); - sb.AppendLine($" dict.TryAdd(\"{escapedKey}\", typeof({typeFullName}));"); + // Remove global:: prefix for the dictionary key so Type.FullName lookups hit the fast path, + // but keep global:: in the typeof() expression to avoid namespace conflicts + var lookupKey = key.Replace("global::", "").Replace("\"", "\\\""); + sb.AppendLine($" dict.TryAdd(\"{lookupKey}\", typeof({typeFullName}));"); } private static string GenerateInterfaceIndices(List types) diff --git a/Rdmp.Core.Tests/CommandExecution/TestCommandsAreSupported.cs b/Rdmp.Core.Tests/CommandExecution/TestCommandsAreSupported.cs index 517e4a68e..5e19899fc 100644 --- a/Rdmp.Core.Tests/CommandExecution/TestCommandsAreSupported.cs +++ b/Rdmp.Core.Tests/CommandExecution/TestCommandsAreSupported.cs @@ -43,6 +43,7 @@ public void Init() public static IEnumerable GetAllCommandTypes() { return MEF.GetTypes() + .Where(t => !t.IsGenericTypeDefinition) .Where(t => !KnownIncompatibleCommands.Contains(t)) .OrderBy(t => t.FullName); }