diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ClassBaseClassInspector.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ClassBaseClassInspector.cs
index ebc246f2099..179c51ec8c6 100644
--- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ClassBaseClassInspector.cs
+++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ClassBaseClassInspector.cs
@@ -5,6 +5,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
+using static System.StringComparison;
using TypeInfo = HotChocolate.Types.Analyzers.Models.TypeInfo;
namespace HotChocolate.Types.Analyzers.Inspectors;
@@ -47,6 +48,14 @@ public bool TryHandle(
return true;
}
+ if (current.GetAttributes().Any(
+ t => t.AttributeClass?.ToDisplayString()
+ .StartsWith(WellKnownAttributes.InterfaceTypeAttribute, Ordinal) is true))
+ {
+ syntaxInfo = new TypeInfo(typeDisplayString);
+ return true;
+ }
+
if (WellKnownTypes.TypeExtensionClass.Contains(displayString))
{
syntaxInfo = new TypeExtensionInfo(typeDisplayString, false);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/TypeModuleSyntaxGeneratorTests.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/TypeModuleSyntaxGeneratorTests.cs
index c4bcd8e7777..004e9754fa1 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/TypeModuleSyntaxGeneratorTests.cs
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/TypeModuleSyntaxGeneratorTests.cs
@@ -236,4 +236,40 @@ internal static partial class RootType
"""
]).MatchMarkdownAsync();
}
+
+ [Fact]
+ public async Task GenerateSource_Interface_Inheritance_Registers_Derived_Implementations()
+ {
+ await TestHelper.GetGeneratedSourceSnapshot(
+ [
+ """
+ using HotChocolate.Types;
+
+ namespace TestNamespace;
+
+ [InterfaceType]
+ public abstract class StatementTransaction
+ {
+ public int Id { get; set; }
+ }
+
+ public sealed class DepositStatementTransaction : StatementTransaction
+ {
+ public decimal CollectionAmount { get; init; }
+ }
+
+ public sealed class BillingStatementTransaction : StatementTransaction
+ {
+ public decimal FeeAndChargeAmount { get; init; }
+ }
+
+ [QueryType]
+ public static partial class Query
+ {
+ public static StatementTransaction GetStatementTransaction()
+ => new DepositStatementTransaction { Id = 1, CollectionAmount = 42m };
+ }
+ """
+ ]).MatchMarkdownAsync();
+ }
}
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/TypeModuleSyntaxGeneratorTests.GenerateSource_Interface_Inheritance_Registers_Derived_Implementations.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/TypeModuleSyntaxGeneratorTests.GenerateSource_Interface_Inheritance_Registers_Derived_Implementations.md
new file mode 100644
index 00000000000..43aed0e47d4
--- /dev/null
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/TypeModuleSyntaxGeneratorTests.GenerateSource_Interface_Inheritance_Registers_Derived_Implementations.md
@@ -0,0 +1,121 @@
+# GenerateSource_Interface_Inheritance_Registers_Derived_Implementations
+
+## 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.AddType();
+ builder.AddType();
+ builder.ConfigureDescriptorContext(ctx => ctx.TypeConfiguration.TryAdd(
+ "Tests::TestNamespace.Query",
+ global::HotChocolate.Types.OperationTypeNames.Query,
+ () => global::TestNamespace.Query.Initialize));
+ builder.AddType();
+ builder.ConfigureSchema(
+ b => b.TryAddRootType(
+ () => new global::HotChocolate.Types.ObjectType(
+ d => d.Name(global::HotChocolate.Types.OperationTypeNames.Query)),
+ HotChocolate.Language.OperationType.Query));
+ return builder;
+ }
+ }
+}
+
+```
+
+## Query.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 Query
+ {
+ internal static void Initialize(global::HotChocolate.Types.IObjectTypeDescriptor descriptor)
+ {
+ var extension = descriptor.Extend();
+ var configuration = extension.Configuration;
+ var thisType = typeof(global::TestNamespace.Query);
+ var bindingResolver = extension.Context.ParameterBindingResolver;
+ var resolvers = new __Resolvers();
+
+ HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration(
+ extension.Context,
+ descriptor,
+ null,
+ new global::HotChocolate.Types.QueryTypeAttribute());
+ configuration.ConfigurationsAreApplied = true;
+
+ var naming = descriptor.Extend().Context.Naming;
+
+ descriptor
+ .Field(naming.GetMemberName("StatementTransaction", 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(global::TestNamespace.StatementTransaction), HotChocolate.Types.TypeContext.Output),
+ new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.NamedTypeNode("global__TestNamespace_StatementTransaction")));
+ configuration.ResultType = typeof(global::TestNamespace.StatementTransaction);
+
+ configuration.SetSourceGeneratorFlags();
+
+ configuration.Resolvers = context.Resolvers.GetStatementTransaction();
+ },
+ (Resolvers: resolvers, ThisType: thisType));
+
+ Configure(descriptor);
+ }
+
+ static partial void Configure(global::HotChocolate.Types.IObjectTypeDescriptor descriptor);
+
+ private sealed class __Resolvers
+ {
+ public HotChocolate.Resolvers.FieldResolverDelegates GetStatementTransaction()
+ {
+ return new global::HotChocolate.Resolvers.FieldResolverDelegates(pureResolver: GetStatementTransaction);
+ }
+
+ private global::System.Object? GetStatementTransaction(global::HotChocolate.Resolvers.IResolverContext context)
+ {
+ var result = global::TestNamespace.Query.GetStatementTransaction();
+ return result;
+ }
+ }
+ }
+}
+
+
+```
diff --git a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/IntegrationTests.cs b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/IntegrationTests.cs
index 468743f4f85..19dcf5bf324 100644
--- a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/IntegrationTests.cs
+++ b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/IntegrationTests.cs
@@ -454,6 +454,38 @@ public async Task Fallback_To_Runtime_Properties_When_No_Field_Is_Bindable()
MatchSnapshot(result, interceptor);
}
+ [Fact]
+ public async Task Query_InterfaceType_Derived_Implementation_Is_Resolved()
+ {
+ // act
+ var result = await ExecuteAsync(
+ """
+ {
+ statementTransaction {
+ __typename
+ id
+ ... on DepositStatementTransaction {
+ collectionAmount
+ }
+ }
+ }
+ """);
+
+ // assert
+ result.MatchInlineSnapshot(
+ """
+ {
+ "data": {
+ "statementTransaction": {
+ "__typename": "DepositStatementTransaction",
+ "id": 1,
+ "collectionAmount": 42
+ }
+ }
+ }
+ """);
+ }
+
[Fact]
public async Task SecondLevelCache_Is_Used()
{
diff --git a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/StatementTransactions/BillingStatementTransaction.cs b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/StatementTransactions/BillingStatementTransaction.cs
new file mode 100644
index 00000000000..ef54f811128
--- /dev/null
+++ b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/StatementTransactions/BillingStatementTransaction.cs
@@ -0,0 +1,6 @@
+namespace HotChocolate.Data.Types.StatementTransactions;
+
+public sealed class BillingStatementTransaction : StatementTransaction
+{
+ public int FeeAndChargeAmount { get; init; }
+}
diff --git a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/StatementTransactions/DepositStatementTransaction.cs b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/StatementTransactions/DepositStatementTransaction.cs
new file mode 100644
index 00000000000..0d5106d54aa
--- /dev/null
+++ b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/StatementTransactions/DepositStatementTransaction.cs
@@ -0,0 +1,6 @@
+namespace HotChocolate.Data.Types.StatementTransactions;
+
+public sealed class DepositStatementTransaction : StatementTransaction
+{
+ public int CollectionAmount { get; init; }
+}
diff --git a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/StatementTransactions/StatementTransaction.cs b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/StatementTransactions/StatementTransaction.cs
new file mode 100644
index 00000000000..878924c70c4
--- /dev/null
+++ b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/StatementTransactions/StatementTransaction.cs
@@ -0,0 +1,9 @@
+using HotChocolate.Types;
+
+namespace HotChocolate.Data.Types.StatementTransactions;
+
+[InterfaceType]
+public abstract class StatementTransaction
+{
+ public int Id { get; init; }
+}
diff --git a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/StatementTransactions/StatementTransactionQueries.cs b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/StatementTransactions/StatementTransactionQueries.cs
new file mode 100644
index 00000000000..0c8e5123383
--- /dev/null
+++ b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/StatementTransactions/StatementTransactionQueries.cs
@@ -0,0 +1,14 @@
+using HotChocolate.Types;
+
+namespace HotChocolate.Data.Types.StatementTransactions;
+
+[QueryType]
+public static partial class StatementTransactionQueries
+{
+ public static StatementTransaction GetStatementTransaction()
+ => new DepositStatementTransaction
+ {
+ Id = 1,
+ CollectionAmount = 42
+ };
+}
diff --git a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/__snapshots__/IntegrationTests.CreateSchema.graphql b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/__snapshots__/IntegrationTests.CreateSchema.graphql
index ea65b2c76c8..a6ec08f1e3c 100644
--- a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/__snapshots__/IntegrationTests.CreateSchema.graphql
+++ b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/__snapshots__/IntegrationTests.CreateSchema.graphql
@@ -7,6 +7,15 @@ interface Node {
id: ID!
}
+interface StatementTransaction {
+ id: Int!
+}
+
+type BillingStatementTransaction implements StatementTransaction {
+ feeAndChargeAmount: Int!
+ id: Int!
+}
+
type Brand implements Node {
products("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String where: ProductFilterInput @cost(weight: "10") order: [ProductSortInput!] @cost(weight: "10")): BrandProductsConnection! @listSize(assumedSize: 50, slicingArguments: [ "first", "last" ], slicingArgumentDefaultValue: 10, sizedFields: [ "edges", "nodes" ], requireOneSlicingArgument: false) @cost(weight: "10")
id: ID!
@@ -71,6 +80,11 @@ type ConnectionPageInfo {
endCursor: String
}
+type DepositStatementTransaction implements StatementTransaction {
+ collectionAmount: Int!
+ id: Int!
+}
+
type ExpressionPerson {
fullName: String @cost(weight: "10")
id: Int!
@@ -148,6 +162,7 @@ type Query {
productById(id: ID!): Product @lookup @internal @cost(weight: "10")
productsNonRelative("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String where: ProductFilterInput @cost(weight: "10") order: [ProductSortInput!] @cost(weight: "10")): ProductConnection! @listSize(assumedSize: 50, slicingArguments: [ "first", "last" ], slicingArgumentDefaultValue: 10, sizedFields: [ "edges", "nodes" ], requireOneSlicingArgument: false) @cost(weight: "10")
singleProperties: [SingleProperty!]! @cost(weight: "10")
+ statementTransaction: StatementTransaction!
}
type SingleProperty {