Skip to content

Commit

Permalink
Enhance enumerable graph type determining (#262)
Browse files Browse the repository at this point in the history
Co-authored-by: Tommy Lillehagen <[email protected]>
  • Loading branch information
vcup and tlil authored Jun 1, 2024
1 parent 9e43f0e commit 257800e
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,46 @@ public static bool IsNullableType(this TypeInfo type)
return type.IsGenericType(typeof(Nullable<>));
}

public static Type GetImplementationInterface(this Type type, Type interfaceType, bool fuseGeneric = true)
{
if (!interfaceType.IsInterface)
return null;

fuseGeneric &= interfaceType.IsGenericType;
if (type.IsGenericType && fuseGeneric
? type.GetGenericTypeDefinition() == interfaceType.GetGenericTypeDefinition()
: type == interfaceType)
{
return interfaceType;
}

while (type is not null)
{
var interfaces = type.GetInterfaces();
var mayFusedGenericInterface = fuseGeneric
? interfaces.Select(t => t.IsGenericType ? t.GetGenericTypeDefinition() : t).ToArray()
: interfaces;

for (int i = 0; i < interfaces.Length; i++)
{
var @interface = interfaces[i];
if (mayFusedGenericInterface[i] == interfaceType)
return interfaces[i];
var ni = @interface.GetImplementationInterface(interfaceType, fuseGeneric);
if (ni is not null)
return ni;
}

type = type.BaseType;
}

return null;
}

public static bool IsImplementingInterface(this Type type, Type interfaceType, bool fuseGeneric = true) =>
type.GetImplementationInterface(interfaceType, fuseGeneric) is not null;


public static TypeInfo BaseType(this TypeInfo type)
{
return type.IsNullableType()
Expand All @@ -45,14 +85,19 @@ public static TypeInfo TypeParameter(this TypeInfo type)
}
return type.IsGenericType
? type.GenericTypeArguments.First().GetTypeInfo()
: null;
: type.TypeParameterCollection();
}

public static TypeInfo TypeParameter(this GraphTypeInfo type)
{
return type.TypeRepresentation.TypeParameter();
}

public static TypeInfo TypeParameterCollection(this TypeInfo type) => (
type.GetImplementationInterface(typeof(ICollection<>)) ??
type.GetImplementationInterface(typeof(IReadOnlyList<>))
)?.GetTypeInfo();

public static bool IsPrimitiveGraphType(this TypeInfo type)
{
return type.IsPrimitive ||
Expand All @@ -63,8 +108,13 @@ public static bool IsPrimitiveGraphType(this TypeInfo type)

public static bool IsEnumerableGraphType(this TypeInfo type)
{
return type.IsGenericType(typeof(List<>)) ||
type.IsGenericType(typeof(IList<>)) ||
if (type.IsImplementingInterface(typeof(IDictionary)) || type.IsImplementingInterface(typeof(IDictionary<,>)))
{
return false;
}

return type.IsImplementingInterface(typeof(ICollection<>)) ||
type.IsImplementingInterface(typeof(IReadOnlyCollection<>)) ||
type.IsGenericType(typeof(IEnumerable<>)) ||
(type.IsGenericType && type.DeclaringType == typeof(Enumerable)) || // Handles internal Iterator implementations for LINQ; for reference https://referencesource.microsoft.com/#system.core/System/Linq/Enumerable.cs
type.IsArray;
Expand Down Expand Up @@ -93,16 +143,19 @@ public static TypeInfo GetTypeRepresentation(this TypeInfo typeInfo)
{
typeInfo = typeInfo.TypeParameter();
}

if (typeInfo.IsGenericType(typeof(IObservable<>)))
{
typeInfo = typeInfo.TypeParameter();
}

if (typeInfo.IsGenericType(typeof(Nullable<>)) ||
typeInfo.IsGenericType(typeof(NonNull<>)) ||
typeInfo.IsGenericType(typeof(Optional<>)))
{
typeInfo = typeInfo.TypeParameter();
}

return typeInfo;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using GraphQL.Conventions.Types.Resolution.Extensions;
using Xunit;

namespace Tests.Types.Resolution.Extensions
{
public class ReflectionExtensionsTests
{
[Theory]
[MemberData(nameof(IsEnumerableGraphType_Should_Return_True_For_Common_Collection_Types_Data))]
public void IsEnumerableGraphType_Should_Return_True_For_Common_Collection_Types(Type type)
{
Assert.IsTrue(type.GetTypeInfo().IsEnumerableGraphType());
}

public static TheoryData<Type> IsEnumerableGraphType_Should_Return_True_For_Common_Collection_Types_Data() => new()
{
typeof(IEnumerable<>),
typeof(ConcurrentQueue<>),
typeof(HashSet<>),
typeof(int[]),
typeof(List<>),
typeof(IList<>),
typeof(IReadOnlyList<>),
typeof(IReadOnlyCollection<>),
};

[Theory]
[MemberData(nameof(IsEnumerableGraphType_Should_Return_False_For_Common_Dictionary_Types_Data))]
public void IsEnumerableGraphType_Should_Return_False_For_Common_Dictionary_Types(Type type)
{
Assert.IsFalse(type.GetTypeInfo().IsEnumerableGraphType());
}

public static TheoryData<Type> IsEnumerableGraphType_Should_Return_False_For_Common_Dictionary_Types_Data() => new()
{
typeof(IDictionary<,>),
typeof(IDictionary),
};

[Theory]
[MemberData(nameof(GetImplementationInterface_WithoutFuse_AcquireSpecifiedInterfaceOnly_Data))]
public void GetImplementationInterface_WithoutFuse_AcquireSpecifiedInterfaceOnly(Type type, Type assignableInterface)
=> Assert.IsTrue(type.IsImplementingInterface(assignableInterface, false));

public static TheoryData<Type, Type> GetImplementationInterface_WithoutFuse_AcquireSpecifiedInterfaceOnly_Data() =>
new()
{
{ typeof(ITestInterface1<int>), typeof(ITestInterface1<int>) },
{ typeof(ITestInterface2<int>), typeof(ITestInterface1<int>) },
{ typeof(ITestInterface2<int>), typeof(ITestInterface2<int>) },
{ typeof(TestClass11<int>), typeof(ITestInterface1<int>) },
{ typeof(TestClass12), typeof(ITestInterface1<string>) },
{ typeof(TestClass12), typeof(ITestInterface2<string>) },
{ typeof(TestClass2), typeof(ITestInterface1<int>) },
{ typeof(TestClass2), typeof(ITestInterface1<string>) },
{ typeof(TestClass2), typeof(ITestInterface2<string>) },
};

[Theory]
[MemberData(nameof(GetImplementationInterface_WithFuse_AcquireSpecifiedInterfaceOnly_Data))]
public void GetImplementationInterface_WithFuse_AcquireSpecifiedInterfaceOnly(Type type, Type assignableInterface) =>
Assert.IsTrue(type.IsImplementingInterface(assignableInterface));

public static TheoryData<Type, Type> GetImplementationInterface_WithFuse_AcquireSpecifiedInterfaceOnly_Data() =>
new()
{
{ typeof(ITestInterface1<int>), typeof(ITestInterface1<>) },
{ typeof(ITestInterface2<int>), typeof(ITestInterface1<>) },
{ typeof(ITestInterface2<int>), typeof(ITestInterface2<>) },
{ typeof(TestClass11<int>), typeof(ITestInterface1<>) },
//
{ typeof(ITestInterface1<string>), typeof(ITestInterface1<>) },
{ typeof(ITestInterface2<string>), typeof(ITestInterface1<>) },
{ typeof(ITestInterface2<string>), typeof(ITestInterface2<>) },
{ typeof(TestClass11<string>), typeof(ITestInterface1<>) },
//
{ typeof(TestClass12), typeof(ITestInterface1<>) },
{ typeof(TestClass12), typeof(ITestInterface2<>) },
{ typeof(TestClass2), typeof(ITestInterface1<>) },
{ typeof(TestClass2), typeof(ITestInterface2<>) },
};

private interface ITestInterface1<out T>
{
// ReSharper disable once UnusedMember.Global
T Item => throw new NotImplementedException();
}

private interface ITestInterface2<out T> : ITestInterface1<T>
{
}

private class TestClass11<T> : ITestInterface1<T>
{
}

private class TestClass12 : ITestInterface2<string>
{
}

private class TestClass2 : ITestInterface2<string>, ITestInterface1<int>
{
}
}
}

0 comments on commit 257800e

Please sign in to comment.