diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/DataTypeDescriptorMapper.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/DataTypeDescriptorMapper.cs index 02bd3c9ef53..c70d8ff0953 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/DataTypeDescriptorMapper.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/DataTypeDescriptorMapper.cs @@ -27,6 +27,26 @@ private static IEnumerable CollectDataTypes( .OfType() .ToList(); + // Index every type by its runtime type name so that component lookups below run in + // O(1) instead of scanning the whole (potentially very large) type list per component. + // A name can map to more than one type; the Single(...) semantics of the original + // lookup are preserved at resolution time (see GetTypeByRuntimeName). + var typesByRuntimeName = + new Dictionary(StringComparer.Ordinal); + + foreach (var type in context.Types) + { + var runtimeName = type.RuntimeType.Name; + if (typesByRuntimeName.TryGetValue(runtimeName, out var match)) + { + typesByRuntimeName[runtimeName] = match.WithAdditional(); + } + else + { + typesByRuntimeName[runtimeName] = new RuntimeTypeMatch(type); + } + } + var dataTypeInfos = new Dictionary(StringComparer.Ordinal); foreach (var dataType in dataTypes) @@ -74,8 +94,7 @@ private static IEnumerable CollectDataTypes( dataTypeInfo.Name, NamingConventions.CreateStateNamespace(context.Namespace), dataTypeInfo.Components - .Select(name => context.Types.Single( - t => t.RuntimeType.Name.Equals(name))) + .Select(name => GetTypeByRuntimeName(typesByRuntimeName, name)) .OfType() .ToList(), implements, @@ -83,6 +102,49 @@ private static IEnumerable CollectDataTypes( } } + private static INamedTypeDescriptor GetTypeByRuntimeName( + Dictionary typesByRuntimeName, + string runtimeName) + { + // Mirror the previous Single(...) semantics: zero matches and more than one match + // both throw, exactly as Enumerable.Single did over the full type list. + if (!typesByRuntimeName.TryGetValue(runtimeName, out var match)) + { + throw new InvalidOperationException( + $"Sequence contains no matching element for runtime type '{runtimeName}'."); + } + + if (match.Count > 1) + { + throw new InvalidOperationException( + $"Sequence contains more than one matching element for runtime type " + + $"'{runtimeName}'."); + } + + return match.First; + } + + private readonly struct RuntimeTypeMatch + { + public RuntimeTypeMatch(INamedTypeDescriptor first) + { + First = first; + Count = 1; + } + + private RuntimeTypeMatch(INamedTypeDescriptor first, int count) + { + First = first; + Count = count; + } + + public INamedTypeDescriptor First { get; } + + public int Count { get; } + + public RuntimeTypeMatch WithAdditional() => new(First, Count + 1); + } + private sealed class DataTypeInfo { public DataTypeInfo(string name, string? description)