diff --git a/src/HotChocolate/Core/src/Types.Analyzers/ParentAttributeAnalyzer.cs b/src/HotChocolate/Core/src/Types.Analyzers/ParentAttributeAnalyzer.cs index d12dede6272..6fbe9af1f4b 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/ParentAttributeAnalyzer.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/ParentAttributeAnalyzer.cs @@ -178,8 +178,7 @@ private static bool IsBatchResolverMethod( if (fullName is "System.Collections.Generic.List" or "System.Collections.Generic.IList" or "System.Collections.Generic.IReadOnlyList" - or "System.Collections.Generic.ICollection" - or "System.Collections.Generic.IEnumerable") + or "System.Collections.Immutable.ImmutableArray") { return namedType.TypeArguments[0]; } diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/ParentAttributeAnalyzerTests.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/ParentAttributeAnalyzerTests.cs index ab7479b779f..81d27ca40e5 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/ParentAttributeAnalyzerTests.cs +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/ParentAttributeAnalyzerTests.cs @@ -294,6 +294,104 @@ public class Brand enableAnalyzers: true).MatchMarkdownAsync(); } + [Fact] + public async Task ParentAttribute_BatchResolver_ImmutableArrayOfParentType_NoError() + { + await TestHelper.GetGeneratedSourceSnapshot( + [""" + using HotChocolate; + using HotChocolate.Types; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Linq; + + namespace TestNamespace; + + [ObjectType] + public static partial class ProductNode + { + [BatchResolver] + public static List GetDisplayName( + [Parent] ImmutableArray products) + => products.Select(p => p.Name).ToList(); + } + + public class Product + { + public int Id { get; set; } + public string Name { get; set; } + } + """], + enableAnalyzers: true).MatchMarkdownAsync(); + } + + [Fact] + public async Task ParentAttribute_BatchResolver_ImmutableArrayOfWrongType_RaisesError() + { + await TestHelper.GetGeneratedSourceSnapshot( + [""" + using HotChocolate; + using HotChocolate.Types; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Linq; + + namespace TestNamespace; + + [ObjectType] + public static partial class ProductNode + { + [BatchResolver] + public static List GetDisplayName( + [Parent] ImmutableArray brands) + => brands.Select(b => b.Name).ToList(); + } + + public class Product + { + public int Id { get; set; } + public string Name { get; set; } + } + + public class Brand + { + public int Id { get; set; } + public string Name { get; set; } + } + """], + enableAnalyzers: true).MatchMarkdownAsync(); + } + + [Fact] + public async Task ParentAttribute_BatchResolver_ArrayOfParentType_NoError() + { + await TestHelper.GetGeneratedSourceSnapshot( + [""" + using HotChocolate; + using HotChocolate.Types; + using System.Collections.Generic; + using System.Linq; + + namespace TestNamespace; + + [ObjectType] + public static partial class ProductNode + { + [BatchResolver] + public static List GetDisplayName( + [Parent] Product[] products) + => products.Select(p => p.Name).ToList(); + } + + public class Product + { + public int Id { get; set; } + public string Name { get; set; } + } + """], + enableAnalyzers: true).MatchMarkdownAsync(); + } + [Fact] public async Task ParentAttribute_WithRequires_TypeMismatch_RaisesError() { diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ParentAttributeAnalyzerTests.ParentAttribute_BatchResolver_ArrayOfParentType_NoError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ParentAttributeAnalyzerTests.ParentAttribute_BatchResolver_ArrayOfParentType_NoError.md new file mode 100644 index 00000000000..181f5cc26c4 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ParentAttributeAnalyzerTests.ParentAttribute_BatchResolver_ArrayOfParentType_NoError.md @@ -0,0 +1,143 @@ +# ParentAttribute_BatchResolver_ArrayOfParentType_NoError + +## HotChocolateTypeModule.735550c.g.cs + +```csharp +// + +#nullable enable +#pragma warning disable + +using System; +using System.Runtime.CompilerServices; +using HotChocolate; +using HotChocolate.Types; +using HotChocolate.Execution.Configuration; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class TestsTypesRequestExecutorBuilderExtensions + { + public static IRequestExecutorBuilder AddTestsTypes(this IRequestExecutorBuilder builder) + { + builder.ConfigureDescriptorContext(ctx => ctx.TypeConfiguration.TryAdd( + "Tests::TestNamespace.ProductNode", + () => global::TestNamespace.ProductNode.Initialize)); + builder.AddType>(); + return builder; + } + } +} + +``` + +## ProductNode.WaAdMHmlGJHjtEI4nqY7WA.hc.g.cs + +```csharp +// + +#nullable enable +#pragma warning disable + +using System; +using System.Runtime.CompilerServices; +using HotChocolate; +using HotChocolate.Types; +using HotChocolate.Execution.Configuration; +using Microsoft.Extensions.DependencyInjection; +using HotChocolate.Internal; + +namespace TestNamespace +{ + public static partial class ProductNode + { + internal static void Initialize(global::HotChocolate.Types.IObjectTypeDescriptor descriptor) + { + var extension = descriptor.Extend(); + var configuration = extension.Configuration; + var thisType = typeof(global::TestNamespace.ProductNode); + var bindingResolver = extension.Context.ParameterBindingResolver; + var resolvers = new __Resolvers(); + + var naming = descriptor.Extend().Context.Naming; + + descriptor + .Field(naming.GetMemberName("DisplayName", global::HotChocolate.Types.MemberKind.ObjectField)) + .ExtendWith(static (field, context) => + { + var configuration = field.Configuration; + var typeInspector = field.Context.TypeInspector; + var bindingResolver = field.Context.ParameterBindingResolver; + var naming = field.Context.Naming; + + configuration.Type = global::HotChocolate.Types.Descriptors.TypeReference.Create( + typeInspector.GetTypeRef(typeof(string), HotChocolate.Types.TypeContext.Output), + new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.NamedTypeNode("string"))); + configuration.ResultType = typeof(string); + + configuration.SetSourceGeneratorFlags(); + configuration.SetBatchResolverFlags(); + + configuration.BatchResolver = context.Resolvers.GetDisplayName(); + }, + (Resolvers: resolvers, ThisType: thisType)); + + Configure(descriptor); + } + + static partial void Configure(global::HotChocolate.Types.IObjectTypeDescriptor descriptor); + + private sealed class __Resolvers + { + public HotChocolate.Resolvers.BatchFieldDelegate GetDisplayName() + => GetDisplayName; + + private global::System.Threading.Tasks.ValueTask GetDisplayName(global::System.Collections.Immutable.ImmutableArray contexts) + { + var args0 = new global::TestNamespace.Product[](contexts.Length); + + for (var i = 0; i < contexts.Length; i++) + { + args0.Add(contexts[i].Parent()); + } + + var result = global::TestNamespace.ProductNode.GetDisplayName(args0); + + if (result is global::System.Collections.IList list) + { + for (var i = 0; i < contexts.Length; i++) + { + contexts[i].Result = i < list.Count ? list[i] : null; + } + } + return default; + } + } + } +} + + +``` + +## Assembly Emit Diagnostics + +```json +[ + { + "Id": "CS1586", + "Title": "", + "Severity": "Error", + "WarningLevel": 0, + "Location": "ProductNode.WaAdMHmlGJHjtEI4nqY7WA.hc.g.cs: (60,61)-(60,63)", + "HelpLinkUri": "https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS1586)", + "MessageFormat": "Array creation must have array size or array initializer", + "Message": "Array creation must have array size or array initializer", + "Category": "Compiler", + "CustomTags": [ + "Compiler", + "Telemetry", + "NotConfigurable" + ] + } +] +``` diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ParentAttributeAnalyzerTests.ParentAttribute_BatchResolver_ImmutableArrayOfParentType_NoError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ParentAttributeAnalyzerTests.ParentAttribute_BatchResolver_ImmutableArrayOfParentType_NoError.md new file mode 100644 index 00000000000..a3576f80857 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ParentAttributeAnalyzerTests.ParentAttribute_BatchResolver_ImmutableArrayOfParentType_NoError.md @@ -0,0 +1,143 @@ +# ParentAttribute_BatchResolver_ImmutableArrayOfParentType_NoError + +## HotChocolateTypeModule.735550c.g.cs + +```csharp +// + +#nullable enable +#pragma warning disable + +using System; +using System.Runtime.CompilerServices; +using HotChocolate; +using HotChocolate.Types; +using HotChocolate.Execution.Configuration; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class TestsTypesRequestExecutorBuilderExtensions + { + public static IRequestExecutorBuilder AddTestsTypes(this IRequestExecutorBuilder builder) + { + builder.ConfigureDescriptorContext(ctx => ctx.TypeConfiguration.TryAdd( + "Tests::TestNamespace.ProductNode", + () => global::TestNamespace.ProductNode.Initialize)); + builder.AddType>(); + return builder; + } + } +} + +``` + +## ProductNode.WaAdMHmlGJHjtEI4nqY7WA.hc.g.cs + +```csharp +// + +#nullable enable +#pragma warning disable + +using System; +using System.Runtime.CompilerServices; +using HotChocolate; +using HotChocolate.Types; +using HotChocolate.Execution.Configuration; +using Microsoft.Extensions.DependencyInjection; +using HotChocolate.Internal; + +namespace TestNamespace +{ + public static partial class ProductNode + { + internal static void Initialize(global::HotChocolate.Types.IObjectTypeDescriptor descriptor) + { + var extension = descriptor.Extend(); + var configuration = extension.Configuration; + var thisType = typeof(global::TestNamespace.ProductNode); + var bindingResolver = extension.Context.ParameterBindingResolver; + var resolvers = new __Resolvers(); + + var naming = descriptor.Extend().Context.Naming; + + descriptor + .Field(naming.GetMemberName("DisplayName", global::HotChocolate.Types.MemberKind.ObjectField)) + .ExtendWith(static (field, context) => + { + var configuration = field.Configuration; + var typeInspector = field.Context.TypeInspector; + var bindingResolver = field.Context.ParameterBindingResolver; + var naming = field.Context.Naming; + + configuration.Type = global::HotChocolate.Types.Descriptors.TypeReference.Create( + typeInspector.GetTypeRef(typeof(string), HotChocolate.Types.TypeContext.Output), + new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.NamedTypeNode("string"))); + configuration.ResultType = typeof(string); + + configuration.SetSourceGeneratorFlags(); + configuration.SetBatchResolverFlags(); + + configuration.BatchResolver = context.Resolvers.GetDisplayName(); + }, + (Resolvers: resolvers, ThisType: thisType)); + + Configure(descriptor); + } + + static partial void Configure(global::HotChocolate.Types.IObjectTypeDescriptor descriptor); + + private sealed class __Resolvers + { + public HotChocolate.Resolvers.BatchFieldDelegate GetDisplayName() + => GetDisplayName; + + private global::System.Threading.Tasks.ValueTask GetDisplayName(global::System.Collections.Immutable.ImmutableArray contexts) + { + var args0 = new global::System.Collections.Immutable.ImmutableArray(contexts.Length); + + for (var i = 0; i < contexts.Length; i++) + { + args0.Add(contexts[i].Parent()); + } + + var result = global::TestNamespace.ProductNode.GetDisplayName(args0); + + if (result is global::System.Collections.IList list) + { + for (var i = 0; i < contexts.Length; i++) + { + contexts[i].Result = i < list.Count ? list[i] : null; + } + } + return default; + } + } + } +} + + +``` + +## Assembly Emit Diagnostics + +```json +[ + { + "Id": "CS1729", + "Title": "", + "Severity": "Error", + "WarningLevel": 0, + "Location": "ProductNode.WaAdMHmlGJHjtEI4nqY7WA.hc.g.cs: (60,32)-(60,114)", + "HelpLinkUri": "https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS1729)", + "MessageFormat": "'{0}' does not contain a constructor that takes {1} arguments", + "Message": "'ImmutableArray' does not contain a constructor that takes 1 arguments", + "Category": "Compiler", + "CustomTags": [ + "Compiler", + "Telemetry", + "NotConfigurable" + ] + } +] +``` diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ParentAttributeAnalyzerTests.ParentAttribute_BatchResolver_ImmutableArrayOfWrongType_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ParentAttributeAnalyzerTests.ParentAttribute_BatchResolver_ImmutableArrayOfWrongType_RaisesError.md new file mode 100644 index 00000000000..3a64e5f3083 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ParentAttributeAnalyzerTests.ParentAttribute_BatchResolver_ImmutableArrayOfWrongType_RaisesError.md @@ -0,0 +1,161 @@ +# ParentAttribute_BatchResolver_ImmutableArrayOfWrongType_RaisesError + +## HotChocolateTypeModule.735550c.g.cs + +```csharp +// + +#nullable enable +#pragma warning disable + +using System; +using System.Runtime.CompilerServices; +using HotChocolate; +using HotChocolate.Types; +using HotChocolate.Execution.Configuration; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class TestsTypesRequestExecutorBuilderExtensions + { + public static IRequestExecutorBuilder AddTestsTypes(this IRequestExecutorBuilder builder) + { + builder.ConfigureDescriptorContext(ctx => ctx.TypeConfiguration.TryAdd( + "Tests::TestNamespace.ProductNode", + () => global::TestNamespace.ProductNode.Initialize)); + builder.AddType>(); + return builder; + } + } +} + +``` + +## ProductNode.WaAdMHmlGJHjtEI4nqY7WA.hc.g.cs + +```csharp +// + +#nullable enable +#pragma warning disable + +using System; +using System.Runtime.CompilerServices; +using HotChocolate; +using HotChocolate.Types; +using HotChocolate.Execution.Configuration; +using Microsoft.Extensions.DependencyInjection; +using HotChocolate.Internal; + +namespace TestNamespace +{ + public static partial class ProductNode + { + internal static void Initialize(global::HotChocolate.Types.IObjectTypeDescriptor descriptor) + { + var extension = descriptor.Extend(); + var configuration = extension.Configuration; + var thisType = typeof(global::TestNamespace.ProductNode); + var bindingResolver = extension.Context.ParameterBindingResolver; + var resolvers = new __Resolvers(); + + var naming = descriptor.Extend().Context.Naming; + + descriptor + .Field(naming.GetMemberName("DisplayName", global::HotChocolate.Types.MemberKind.ObjectField)) + .ExtendWith(static (field, context) => + { + var configuration = field.Configuration; + var typeInspector = field.Context.TypeInspector; + var bindingResolver = field.Context.ParameterBindingResolver; + var naming = field.Context.Naming; + + configuration.Type = global::HotChocolate.Types.Descriptors.TypeReference.Create( + typeInspector.GetTypeRef(typeof(string), HotChocolate.Types.TypeContext.Output), + new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.NamedTypeNode("string"))); + configuration.ResultType = typeof(string); + + configuration.SetSourceGeneratorFlags(); + configuration.SetBatchResolverFlags(); + + configuration.BatchResolver = context.Resolvers.GetDisplayName(); + }, + (Resolvers: resolvers, ThisType: thisType)); + + Configure(descriptor); + } + + static partial void Configure(global::HotChocolate.Types.IObjectTypeDescriptor descriptor); + + private sealed class __Resolvers + { + public HotChocolate.Resolvers.BatchFieldDelegate GetDisplayName() + => GetDisplayName; + + private global::System.Threading.Tasks.ValueTask GetDisplayName(global::System.Collections.Immutable.ImmutableArray contexts) + { + var args0 = new global::System.Collections.Immutable.ImmutableArray(contexts.Length); + + for (var i = 0; i < contexts.Length; i++) + { + args0.Add(contexts[i].Parent()); + } + + var result = global::TestNamespace.ProductNode.GetDisplayName(args0); + + if (result is global::System.Collections.IList list) + { + for (var i = 0; i < contexts.Length; i++) + { + contexts[i].Result = i < list.Count ? list[i] : null; + } + } + return default; + } + } + } +} + + +``` + +## Analyzer Diagnostics + +```json +[ + { + "Id": "HC0097", + "Title": "Parent Attribute Type Mismatch", + "Severity": "Error", + "WarningLevel": 0, + "Location": ": (13,17)-(13,38)", + "MessageFormat": "The parameter type '{0}' must be '{1}' or a base type/interface that '{1}' implements", + "Message": "The parameter type 'ImmutableArray' must be 'Product' or a base type/interface that 'Product' implements", + "Category": "TypeSystem", + "CustomTags": [] + } +] +``` + +## Assembly Emit Diagnostics + +```json +[ + { + "Id": "CS1729", + "Title": "", + "Severity": "Error", + "WarningLevel": 0, + "Location": "ProductNode.WaAdMHmlGJHjtEI4nqY7WA.hc.g.cs: (60,32)-(60,112)", + "HelpLinkUri": "https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS1729)", + "MessageFormat": "'{0}' does not contain a constructor that takes {1} arguments", + "Message": "'ImmutableArray' does not contain a constructor that takes 1 arguments", + "Category": "Compiler", + "CustomTags": [ + "Compiler", + "Telemetry", + "NotConfigurable" + ] + } +] +``` diff --git a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/Brands/BrandNode.cs b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/Brands/BrandNode.cs index 99aba01a60f..7a377f362b0 100644 --- a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/Brands/BrandNode.cs +++ b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/Brands/BrandNode.cs @@ -40,7 +40,7 @@ public static async Task> GetProductCountAsync( [Service] CatalogContext context, CancellationToken cancellationToken) { - var brandIds = brands.Select(b => b.Id).ToList(); + var brandIds = brands.ConvertAll(b => b.Id); var counts = await context.Products .Where(p => brandIds.Contains(p.BrandId)) @@ -48,7 +48,7 @@ public static async Task> GetProductCountAsync( .Select(g => new { BrandId = g.Key, Count = g.Count() }) .ToDictionaryAsync(g => g.BrandId, g => g.Count, cancellationToken); - return brands.Select(b => counts.GetValueOrDefault(b.Id, 0)).ToList(); + return brands.ConvertAll(b => counts.GetValueOrDefault(b.Id, 0)); } [BindMember(nameof(Brand.SupplierId))]