diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/TypeDescriptorMapper.InputTypes.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/TypeDescriptorMapper.InputTypes.cs index 4b053b0721c..595c292f3ce 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/TypeDescriptorMapper.InputTypes.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/TypeDescriptorMapper.InputTypes.cs @@ -2,6 +2,7 @@ using HotChocolate.Utilities; using StrawberryShake.CodeGeneration.Analyzers.Models; using StrawberryShake.CodeGeneration.Descriptors.TypeDescriptors; +using static System.StringComparer; namespace StrawberryShake.CodeGeneration.Mappers; @@ -33,6 +34,21 @@ private static void AddInputTypeProperties( Dictionary typeDescriptors, Dictionary leafTypeDescriptors) { + // Index the descriptors by their GraphQL type name so that resolving a + // field's input type is a constant-time lookup instead of a linear scan + // per field. The descriptors are keyed by class name (Model.Name) which + // can differ from the GraphQL type name (Model.Type.Name) used here, so a + // dedicated index is required. First insertion wins, mirroring the prior + // First() enumeration order. + var descriptorsByTypeName = + new Dictionary(typeDescriptors.Count, Ordinal); + foreach (var typeDescriptorModel in typeDescriptors.Values) + { + descriptorsByTypeName.TryAdd( + typeDescriptorModel.Model.Type.Name, + typeDescriptorModel.Descriptor); + } + foreach (var typeDescriptorModel in typeDescriptors.Values) { var properties = new List(); @@ -50,7 +66,7 @@ private static void AddInputTypeProperties( { fieldType = GetInputTypeDescriptor( field.Type.NamedType(), - typeDescriptors); + descriptorsByTypeName); } properties.Add( @@ -69,10 +85,8 @@ private static void AddInputTypeProperties( private static INamedTypeDescriptor GetInputTypeDescriptor( ITypeDefinition fieldNamedType, - Dictionary typeDescriptors) + Dictionary descriptorsByTypeName) { - return typeDescriptors.Values - .First(t => t.Model.Type.Name.EqualsOrdinal(fieldNamedType.Name)) - .Descriptor; + return descriptorsByTypeName[fieldNamedType.Name]; } } diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/TypeDescriptorMapper.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/TypeDescriptorMapper.cs index bada4a415c8..e6cfb2900dc 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/TypeDescriptorMapper.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/TypeDescriptorMapper.cs @@ -385,6 +385,37 @@ private static void AddProperties( Dictionary typeDescriptors, Dictionary leafTypeDescriptors) { + // Index the descriptors so that resolving a field's output type is a + // constant-time lookup instead of a linear scan over every operation. + // + // descriptorsByModel maps each output type model to its descriptor and is + // used by the operation-scanning fallback in GetFieldTypeDescriptor. + // + // interfaceDescriptorsBySelectionSet maps an interface output type's + // selection set to its descriptor. A field whose result is an interface is + // resolved by the selection set its syntax node points at, which uniquely + // identifies the registered interface descriptor. Selection set nodes are + // compared by reference (the analyzer interns them), and the first + // registered descriptor for a given node wins, mirroring the descriptor + // registration order in CollectTypes. + var descriptorsByModel = + new Dictionary( + typeDescriptors.Count); + var interfaceDescriptorsBySelectionSet = + new Dictionary(); + + foreach (var typeDescriptorModel in typeDescriptors.Values) + { + descriptorsByModel[typeDescriptorModel.Model] = typeDescriptorModel.Descriptor; + + if (typeDescriptorModel.Model.IsInterface) + { + interfaceDescriptorsBySelectionSet.TryAdd( + typeDescriptorModel.Model.SelectionSet, + typeDescriptorModel.Descriptor); + } + } + foreach (var typeDescriptorModel in typeDescriptors.Values.ToList()) { var properties = new List(); @@ -399,13 +430,20 @@ private static void AddProperties( { fieldType = leafTypeDescriptors[namedType.Name]; } + else if (field.SyntaxNode.SelectionSet is { } selectionSet + && interfaceDescriptorsBySelectionSet.TryGetValue( + selectionSet, + out var interfaceDescriptor)) + { + fieldType = interfaceDescriptor; + } else { fieldType = GetFieldTypeDescriptor( model, field.SyntaxNode, field.Type.NamedType(), - typeDescriptors); + descriptorsByModel); } var propertyKind = includeOrSkipDirective @@ -468,7 +506,7 @@ private static INamedTypeDescriptor GetFieldTypeDescriptor( ClientModel model, FieldNode fieldSyntax, ITypeDefinition fieldNamedType, - Dictionary typeDescriptors) + Dictionary descriptorsByModel) { foreach (var operation in model.Operations) { @@ -477,9 +515,7 @@ private static INamedTypeDescriptor GetFieldTypeDescriptor( fieldNamedType, out var fieldType)) { - return typeDescriptors.Values - .First(t => t.Model == fieldType) - .Descriptor; + return descriptorsByModel[fieldType]; } }