Skip to content

Commit 257800e

Browse files
vcuptlil
andauthored
Enhance enumerable graph type determining (#262)
Co-authored-by: Tommy Lillehagen <tommy@lillehagen.me>
1 parent 9e43f0e commit 257800e

File tree

2 files changed

+166
-3
lines changed

2 files changed

+166
-3
lines changed

src/GraphQL.Conventions/Types/Resolution/Extensions/ReflectionExtensions.cs

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,46 @@ public static bool IsNullableType(this TypeInfo type)
3030
return type.IsGenericType(typeof(Nullable<>));
3131
}
3232

33+
public static Type GetImplementationInterface(this Type type, Type interfaceType, bool fuseGeneric = true)
34+
{
35+
if (!interfaceType.IsInterface)
36+
return null;
37+
38+
fuseGeneric &= interfaceType.IsGenericType;
39+
if (type.IsGenericType && fuseGeneric
40+
? type.GetGenericTypeDefinition() == interfaceType.GetGenericTypeDefinition()
41+
: type == interfaceType)
42+
{
43+
return interfaceType;
44+
}
45+
46+
while (type is not null)
47+
{
48+
var interfaces = type.GetInterfaces();
49+
var mayFusedGenericInterface = fuseGeneric
50+
? interfaces.Select(t => t.IsGenericType ? t.GetGenericTypeDefinition() : t).ToArray()
51+
: interfaces;
52+
53+
for (int i = 0; i < interfaces.Length; i++)
54+
{
55+
var @interface = interfaces[i];
56+
if (mayFusedGenericInterface[i] == interfaceType)
57+
return interfaces[i];
58+
var ni = @interface.GetImplementationInterface(interfaceType, fuseGeneric);
59+
if (ni is not null)
60+
return ni;
61+
}
62+
63+
type = type.BaseType;
64+
}
65+
66+
return null;
67+
}
68+
69+
public static bool IsImplementingInterface(this Type type, Type interfaceType, bool fuseGeneric = true) =>
70+
type.GetImplementationInterface(interfaceType, fuseGeneric) is not null;
71+
72+
3373
public static TypeInfo BaseType(this TypeInfo type)
3474
{
3575
return type.IsNullableType()
@@ -45,14 +85,19 @@ public static TypeInfo TypeParameter(this TypeInfo type)
4585
}
4686
return type.IsGenericType
4787
? type.GenericTypeArguments.First().GetTypeInfo()
48-
: null;
88+
: type.TypeParameterCollection();
4989
}
5090

5191
public static TypeInfo TypeParameter(this GraphTypeInfo type)
5292
{
5393
return type.TypeRepresentation.TypeParameter();
5494
}
5595

96+
public static TypeInfo TypeParameterCollection(this TypeInfo type) => (
97+
type.GetImplementationInterface(typeof(ICollection<>)) ??
98+
type.GetImplementationInterface(typeof(IReadOnlyList<>))
99+
)?.GetTypeInfo();
100+
56101
public static bool IsPrimitiveGraphType(this TypeInfo type)
57102
{
58103
return type.IsPrimitive ||
@@ -63,8 +108,13 @@ public static bool IsPrimitiveGraphType(this TypeInfo type)
63108

64109
public static bool IsEnumerableGraphType(this TypeInfo type)
65110
{
66-
return type.IsGenericType(typeof(List<>)) ||
67-
type.IsGenericType(typeof(IList<>)) ||
111+
if (type.IsImplementingInterface(typeof(IDictionary)) || type.IsImplementingInterface(typeof(IDictionary<,>)))
112+
{
113+
return false;
114+
}
115+
116+
return type.IsImplementingInterface(typeof(ICollection<>)) ||
117+
type.IsImplementingInterface(typeof(IReadOnlyCollection<>)) ||
68118
type.IsGenericType(typeof(IEnumerable<>)) ||
69119
(type.IsGenericType && type.DeclaringType == typeof(Enumerable)) || // Handles internal Iterator implementations for LINQ; for reference https://referencesource.microsoft.com/#system.core/System/Linq/Enumerable.cs
70120
type.IsArray;
@@ -93,16 +143,19 @@ public static TypeInfo GetTypeRepresentation(this TypeInfo typeInfo)
93143
{
94144
typeInfo = typeInfo.TypeParameter();
95145
}
146+
96147
if (typeInfo.IsGenericType(typeof(IObservable<>)))
97148
{
98149
typeInfo = typeInfo.TypeParameter();
99150
}
151+
100152
if (typeInfo.IsGenericType(typeof(Nullable<>)) ||
101153
typeInfo.IsGenericType(typeof(NonNull<>)) ||
102154
typeInfo.IsGenericType(typeof(Optional<>)))
103155
{
104156
typeInfo = typeInfo.TypeParameter();
105157
}
158+
106159
return typeInfo;
107160
}
108161

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Concurrent;
4+
using System.Collections.Generic;
5+
using System.Reflection;
6+
using GraphQL.Conventions.Types.Resolution.Extensions;
7+
using Xunit;
8+
9+
namespace Tests.Types.Resolution.Extensions
10+
{
11+
public class ReflectionExtensionsTests
12+
{
13+
[Theory]
14+
[MemberData(nameof(IsEnumerableGraphType_Should_Return_True_For_Common_Collection_Types_Data))]
15+
public void IsEnumerableGraphType_Should_Return_True_For_Common_Collection_Types(Type type)
16+
{
17+
Assert.IsTrue(type.GetTypeInfo().IsEnumerableGraphType());
18+
}
19+
20+
public static TheoryData<Type> IsEnumerableGraphType_Should_Return_True_For_Common_Collection_Types_Data() => new()
21+
{
22+
typeof(IEnumerable<>),
23+
typeof(ConcurrentQueue<>),
24+
typeof(HashSet<>),
25+
typeof(int[]),
26+
typeof(List<>),
27+
typeof(IList<>),
28+
typeof(IReadOnlyList<>),
29+
typeof(IReadOnlyCollection<>),
30+
};
31+
32+
[Theory]
33+
[MemberData(nameof(IsEnumerableGraphType_Should_Return_False_For_Common_Dictionary_Types_Data))]
34+
public void IsEnumerableGraphType_Should_Return_False_For_Common_Dictionary_Types(Type type)
35+
{
36+
Assert.IsFalse(type.GetTypeInfo().IsEnumerableGraphType());
37+
}
38+
39+
public static TheoryData<Type> IsEnumerableGraphType_Should_Return_False_For_Common_Dictionary_Types_Data() => new()
40+
{
41+
typeof(IDictionary<,>),
42+
typeof(IDictionary),
43+
};
44+
45+
[Theory]
46+
[MemberData(nameof(GetImplementationInterface_WithoutFuse_AcquireSpecifiedInterfaceOnly_Data))]
47+
public void GetImplementationInterface_WithoutFuse_AcquireSpecifiedInterfaceOnly(Type type, Type assignableInterface)
48+
=> Assert.IsTrue(type.IsImplementingInterface(assignableInterface, false));
49+
50+
public static TheoryData<Type, Type> GetImplementationInterface_WithoutFuse_AcquireSpecifiedInterfaceOnly_Data() =>
51+
new()
52+
{
53+
{ typeof(ITestInterface1<int>), typeof(ITestInterface1<int>) },
54+
{ typeof(ITestInterface2<int>), typeof(ITestInterface1<int>) },
55+
{ typeof(ITestInterface2<int>), typeof(ITestInterface2<int>) },
56+
{ typeof(TestClass11<int>), typeof(ITestInterface1<int>) },
57+
{ typeof(TestClass12), typeof(ITestInterface1<string>) },
58+
{ typeof(TestClass12), typeof(ITestInterface2<string>) },
59+
{ typeof(TestClass2), typeof(ITestInterface1<int>) },
60+
{ typeof(TestClass2), typeof(ITestInterface1<string>) },
61+
{ typeof(TestClass2), typeof(ITestInterface2<string>) },
62+
};
63+
64+
[Theory]
65+
[MemberData(nameof(GetImplementationInterface_WithFuse_AcquireSpecifiedInterfaceOnly_Data))]
66+
public void GetImplementationInterface_WithFuse_AcquireSpecifiedInterfaceOnly(Type type, Type assignableInterface) =>
67+
Assert.IsTrue(type.IsImplementingInterface(assignableInterface));
68+
69+
public static TheoryData<Type, Type> GetImplementationInterface_WithFuse_AcquireSpecifiedInterfaceOnly_Data() =>
70+
new()
71+
{
72+
{ typeof(ITestInterface1<int>), typeof(ITestInterface1<>) },
73+
{ typeof(ITestInterface2<int>), typeof(ITestInterface1<>) },
74+
{ typeof(ITestInterface2<int>), typeof(ITestInterface2<>) },
75+
{ typeof(TestClass11<int>), typeof(ITestInterface1<>) },
76+
//
77+
{ typeof(ITestInterface1<string>), typeof(ITestInterface1<>) },
78+
{ typeof(ITestInterface2<string>), typeof(ITestInterface1<>) },
79+
{ typeof(ITestInterface2<string>), typeof(ITestInterface2<>) },
80+
{ typeof(TestClass11<string>), typeof(ITestInterface1<>) },
81+
//
82+
{ typeof(TestClass12), typeof(ITestInterface1<>) },
83+
{ typeof(TestClass12), typeof(ITestInterface2<>) },
84+
{ typeof(TestClass2), typeof(ITestInterface1<>) },
85+
{ typeof(TestClass2), typeof(ITestInterface2<>) },
86+
};
87+
88+
private interface ITestInterface1<out T>
89+
{
90+
// ReSharper disable once UnusedMember.Global
91+
T Item => throw new NotImplementedException();
92+
}
93+
94+
private interface ITestInterface2<out T> : ITestInterface1<T>
95+
{
96+
}
97+
98+
private class TestClass11<T> : ITestInterface1<T>
99+
{
100+
}
101+
102+
private class TestClass12 : ITestInterface2<string>
103+
{
104+
}
105+
106+
private class TestClass2 : ITestInterface2<string>, ITestInterface1<int>
107+
{
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)