diff --git a/src/HotChocolate/Core/src/Types.OffsetPagination/Extensions/OffsetPagingObjectFieldDescriptorExtensions.cs b/src/HotChocolate/Core/src/Types.OffsetPagination/Extensions/OffsetPagingObjectFieldDescriptorExtensions.cs
index f51e20c1142..440fde9dee5 100644
--- a/src/HotChocolate/Core/src/Types.OffsetPagination/Extensions/OffsetPagingObjectFieldDescriptorExtensions.cs
+++ b/src/HotChocolate/Core/src/Types.OffsetPagination/Extensions/OffsetPagingObjectFieldDescriptorExtensions.cs
@@ -119,19 +119,30 @@ public static IObjectFieldDescriptor UseOffsetPaging(
? c.TypeInspector.GetTypeRef(itemType)
: null;
- if (typeRef is null
- && d.Type is SyntaxTypeReference syntaxTypeRef
- && syntaxTypeRef.Type.IsListType())
+ if (typeRef is null)
{
- typeRef = syntaxTypeRef.WithType(syntaxTypeRef.Type.ElementType());
- }
+ var currentTypeRef = d.Type;
- if (typeRef is null
- && d.Type is ExtendedTypeReference extendedTypeRef
- && c.TypeInspector.TryCreateTypeInfo(extendedTypeRef.Type, out var typeInfo)
- && GetElementType(typeInfo) is { } elementType)
- {
- typeRef = TypeReference.Create(elementType, TypeContext.Output);
+ if (currentTypeRef is FactoryTypeReference factoryTypeRef
+ && factoryTypeRef.TypeStructure.IsListType())
+ {
+ typeRef = factoryTypeRef.TypeDefinition;
+ }
+
+ if (typeRef is null
+ && currentTypeRef is SyntaxTypeReference syntaxTypeRef
+ && syntaxTypeRef.Type.IsListType())
+ {
+ typeRef = syntaxTypeRef.WithType(syntaxTypeRef.Type.ElementType());
+ }
+
+ if (typeRef is null
+ && currentTypeRef is ExtendedTypeReference extendedTypeRef
+ && c.TypeInspector.TryCreateTypeInfo(extendedTypeRef.Type, out var typeInfo)
+ && GetElementType(typeInfo) is { } elementType)
+ {
+ typeRef = TypeReference.Create(elementType, TypeContext.Output);
+ }
}
var resolverMember = d.ResolverMember ?? d.Member;
@@ -247,12 +258,14 @@ public static IInterfaceFieldDescriptor UseOffsetPaging(
{
var currentTypeRef = d.Type;
- if (currentTypeRef is FactoryTypeReference factoryTypeRef)
+ if (currentTypeRef is FactoryTypeReference factoryTypeRef
+ && factoryTypeRef.TypeStructure.IsListType())
{
- currentTypeRef = factoryTypeRef.GetElementType();
+ typeRef = factoryTypeRef.TypeDefinition;
}
- if (currentTypeRef is ExtendedTypeReference extendedTypeRef
+ if (typeRef is null
+ && currentTypeRef is ExtendedTypeReference extendedTypeRef
&& c.TypeInspector.TryCreateTypeInfo(extendedTypeRef.Type, out var typeInfo)
&& GetElementType(typeInfo) is { } elementType)
{
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/HotChocolate.Types.Analyzers.Tests.csproj b/src/HotChocolate/Core/test/Types.Analyzers.Tests/HotChocolate.Types.Analyzers.Tests.csproj
index f4438cf86d2..78a45915e2a 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/HotChocolate.Types.Analyzers.Tests.csproj
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/HotChocolate.Types.Analyzers.Tests.csproj
@@ -20,6 +20,7 @@
+
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/SourceGeneratorOffsetPagingReproTests.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/SourceGeneratorOffsetPagingReproTests.cs
new file mode 100644
index 00000000000..665e1a7c15f
--- /dev/null
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/SourceGeneratorOffsetPagingReproTests.cs
@@ -0,0 +1,185 @@
+using System.Reflection;
+using System.Runtime.Loader;
+using Basic.Reference.Assemblies;
+using GreenDonut;
+using GreenDonut.Data;
+using HotChocolate.Data.Filters;
+using HotChocolate.Execution;
+using HotChocolate.Execution.Configuration;
+using HotChocolate.Execution.Processing;
+using HotChocolate.Features;
+using HotChocolate.Language;
+using HotChocolate.Types.Analyzers;
+using HotChocolate.Types.Pagination;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace HotChocolate.Types;
+
+public class SourceGeneratorOffsetPagingReproTests
+{
+ [Fact]
+ public async Task QueryType_SourceGenerator_Path_Works_Like_AddQueryType_Path()
+ {
+ var assembly = CompileReproAssembly();
+
+ var sourceGeneratorException = await BuildSchemaWithSourceGeneratorRegistrationAsync(assembly);
+ var addQueryTypeException = await BuildSchemaWithAddQueryTypeRegistrationAsync(assembly);
+
+ Assert.Null(sourceGeneratorException);
+ Assert.Null(addQueryTypeException);
+ }
+
+ private static async Task BuildSchemaWithSourceGeneratorRegistrationAsync(Assembly assembly)
+ {
+ var services = new ServiceCollection();
+ var builder = services.AddGraphQLServer(disableDefaultSecurity: true);
+
+ var addTypesMethod = assembly
+ .GetTypes()
+ .Where(t => t is { IsAbstract: true, IsSealed: true }
+ && t.Namespace == "Microsoft.Extensions.DependencyInjection")
+ .SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.Static))
+ .Single(m =>
+ {
+ var p = m.GetParameters();
+ return m.Name.StartsWith("Add", StringComparison.Ordinal)
+ && m.Name.EndsWith("Types", StringComparison.Ordinal)
+ && m.ReturnType == typeof(IRequestExecutorBuilder)
+ && p.Length == 1
+ && p[0].ParameterType == typeof(IRequestExecutorBuilder);
+ });
+
+ addTypesMethod.Invoke(null, [builder]);
+
+ return await Record.ExceptionAsync(
+ async () => await builder.BuildSchemaAsync());
+ }
+
+ private static async Task BuildSchemaWithAddQueryTypeRegistrationAsync(Assembly assembly)
+ {
+ var runtimeQueryType = assembly.GetType("Repro.RuntimeQuery")
+ ?? throw new InvalidOperationException("Could not locate runtime query type.");
+
+ var builder = new ServiceCollection()
+ .AddGraphQLServer(disableDefaultSecurity: true)
+ .AddQueryType(runtimeQueryType);
+
+ return await Record.ExceptionAsync(
+ async () => await builder.BuildSchemaAsync());
+ }
+
+ private static Assembly CompileReproAssembly()
+ {
+ const string source = """
+ using System.Collections.Generic;
+ using System.Threading.Tasks;
+ using HotChocolate.Types;
+
+ namespace Repro;
+
+ [QueryType]
+ public static partial class SourceGeneratedQuery
+ {
+ [UseOffsetPaging]
+ public static async Task> UglyLegacyResolver()
+ {
+ await Task.Yield();
+ return new();
+ }
+ }
+
+ public class RuntimeQuery
+ {
+ [UseOffsetPaging]
+ public async Task> UglyLegacyResolver()
+ {
+ await Task.Yield();
+ return new();
+ }
+ }
+ """;
+
+ var parseOptions = CSharpParseOptions.Default;
+ var syntaxTree = CSharpSyntaxTree.ParseText(source, parseOptions);
+
+ IEnumerable references =
+ [
+#if NET8_0
+ .. Net80.References.All,
+#elif NET9_0
+ .. Net90.References.All,
+#elif NET10_0
+ .. Net100.References.All,
+#endif
+ MetadataReference.CreateFromFile(typeof(ITypeSystemMember).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(RequestDelegate).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(RequestContext).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(HotChocolateExecutionSelectionExtensions).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(IRequestExecutorBuilder).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(ISelection).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(QueryTypeAttribute).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(Connection).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(PageConnection<>).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(ISchemaDefinition).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(IFeatureProvider).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(OperationType).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(ParentAttribute).Assembly.Location),
+ MetadataReference.CreateFromFile(
+ typeof(HotChocolateAspNetCoreServiceCollectionExtensions).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(DataLoaderBase<,>).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(IDataLoader).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(PagingArguments).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(IPredicateBuilder).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(DefaultPredicateBuilder).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(IFilterContext).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(WebApplication).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(IServiceCollection).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(Microsoft.AspNetCore.Authorization.AuthorizeAttribute).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(Authorization.AuthorizeAttribute).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(UseOffsetPagingAttribute).Assembly.Location)
+ ];
+
+ var compilation = CSharpCompilation.Create(
+ assemblyName: "SourceGeneratorOffsetPagingRepro",
+ syntaxTrees: [syntaxTree],
+ references: references,
+ options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+
+ var driver = CSharpGeneratorDriver
+ .Create(new GraphQLServerGenerator())
+ .RunGenerators(compilation);
+
+ var generatedTrees = driver
+ .GetRunResult()
+ .Results
+ .SelectMany(t => t.GeneratedSources)
+ .Select(s => CSharpSyntaxTree.ParseText(
+ s.SourceText,
+ parseOptions,
+ path: s.HintName));
+
+ var updatedCompilation = compilation.AddSyntaxTrees(generatedTrees);
+
+ using var stream = new MemoryStream();
+ var emitResult = updatedCompilation.Emit(stream);
+
+ if (!emitResult.Success)
+ {
+ throw new InvalidOperationException(
+ string.Join(
+ Environment.NewLine,
+ emitResult.Diagnostics
+ .OrderBy(d => d.Severity)
+ .ThenBy(d => d.Id)
+ .Select(d => d.ToString())));
+ }
+
+ stream.Position = 0;
+
+ var context = new AssemblyLoadContext("SourceGeneratorOffsetPagingRepro", isCollectible: true);
+ return context.LoadFromStream(stream);
+ }
+}
diff --git a/src/HotChocolate/Core/test/Types.OffsetPagination.Tests/IntegrationTests.cs b/src/HotChocolate/Core/test/Types.OffsetPagination.Tests/IntegrationTests.cs
index 10160c0e9aa..70bb9026936 100644
--- a/src/HotChocolate/Core/test/Types.OffsetPagination.Tests/IntegrationTests.cs
+++ b/src/HotChocolate/Core/test/Types.OffsetPagination.Tests/IntegrationTests.cs
@@ -695,6 +695,20 @@ public async Task Simple_EnumerableValueType_ReturnsError()
Assert.Equal("Cannot handle the specified data source.", error.Message);
}
+ [Fact]
+ public async Task Attribute_Dictionary_ReturnType_ThrowsSchemaException()
+ {
+ // act
+ var schema =
+ await new ServiceCollection()
+ .AddGraphQL()
+ .AddQueryType()
+ .BuildSchemaAsync();
+
+ // assert
+ schema.MatchSnapshot();
+ }
+
public class QueryType : ObjectType
{
protected override void Configure(IObjectTypeDescriptor descriptor)
@@ -830,6 +844,16 @@ public ImmutableArray Test()
return [];
}
}
+
+ public class UglyLegacyQuery
+ {
+ [UseOffsetPaging]
+ public async Task> UglyLegacyResolver()
+ {
+ await Task.Yield();
+ return [];
+ }
+ }
}
public class MockExecutable(IQueryable source) : IExecutable
diff --git a/src/HotChocolate/Core/test/Types.OffsetPagination.Tests/__snapshots__/IntegrationTests.Attribute_Dictionary_ReturnType_ThrowsSchemaException.graphql b/src/HotChocolate/Core/test/Types.OffsetPagination.Tests/__snapshots__/IntegrationTests.Attribute_Dictionary_ReturnType_ThrowsSchemaException.graphql
new file mode 100644
index 00000000000..7b0c7cc89a7
--- /dev/null
+++ b/src/HotChocolate/Core/test/Types.OffsetPagination.Tests/__snapshots__/IntegrationTests.Attribute_Dictionary_ReturnType_ThrowsSchemaException.graphql
@@ -0,0 +1,28 @@
+schema {
+ query: UglyLegacyQuery
+}
+
+"Information about the offset pagination."
+type CollectionSegmentInfo {
+ "Indicates whether more items exist following the set defined by the clients arguments."
+ hasNextPage: Boolean!
+ "Indicates whether more items exist prior the set defined by the clients arguments."
+ hasPreviousPage: Boolean!
+}
+
+type KeyValuePairOfStringAndString {
+ key: String!
+ value: String!
+}
+
+type UglyLegacyQuery {
+ uglyLegacyResolver(skip: Int take: Int): UglyLegacyResolverCollectionSegment
+}
+
+"A segment of a collection."
+type UglyLegacyResolverCollectionSegment {
+ "Information to aid in pagination."
+ pageInfo: CollectionSegmentInfo!
+ "A flattened list of the items."
+ items: [KeyValuePairOfStringAndString!]
+}