Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# GenerateSource_Interface_Inheritance_Registers_Derived_Implementations

## HotChocolateTypeModule.735550c.g.cs

```csharp
// <auto-generated/>

#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<global::TestNamespace.BillingStatementTransaction>();
builder.AddType<global::TestNamespace.DepositStatementTransaction>();
builder.ConfigureDescriptorContext(ctx => ctx.TypeConfiguration.TryAdd(
"Tests::TestNamespace.Query",
global::HotChocolate.Types.OperationTypeNames.Query,
() => global::TestNamespace.Query.Initialize));
builder.AddType<global::TestNamespace.StatementTransaction>();
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
// <auto-generated/>

#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;
}
}
}
}


```
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace HotChocolate.Data.Types.StatementTransactions;

public sealed class BillingStatementTransaction : StatementTransaction
{
public int FeeAndChargeAmount { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace HotChocolate.Data.Types.StatementTransactions;

public sealed class DepositStatementTransaction : StatementTransaction
{
public int CollectionAmount { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using HotChocolate.Types;

namespace HotChocolate.Data.Types.StatementTransactions;

[InterfaceType]
public abstract class StatementTransaction
{
public int Id { get; init; }
}
Original file line number Diff line number Diff line change
@@ -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
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down Expand Up @@ -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!
Expand Down Expand Up @@ -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 {
Expand Down
Loading