Skip to content

Commit

Permalink
Merge pull request #49 from EamonNerbonne/eamon/detect-type-inference…
Browse files Browse the repository at this point in the history
…-failures

Detect type inference failures and use explicit types when necessary
  • Loading branch information
EamonNerbonne committed Mar 20, 2015
2 parents 7856aa8 + be5cfa4 commit 49f65ad
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 32 deletions.
47 changes: 22 additions & 25 deletions ExpressionToCodeLib/ExpressionToCodeImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -309,22 +309,24 @@ string CreateGenericArgumentsIfNecessary(MethodCallExpression mce, MethodInfo me
}

if(!explicitMethodTypeArgs) {
var todo = mce.Arguments.Select(argExpr => argExpr.Type).ToList();
var possiblyInferrableTypes = new HashSet<Type>();
Type next;
while(PopFromList(todo, out next)) {
if(!possiblyInferrableTypes.Add(next)) {
continue;
}
todo.AddRange(next.GetInterfaces());
if(next.IsArray) {
todo.Add(next.GetElementType());
} else if(next.IsGenericType) {
todo.AddRange(next.GetGenericArguments());
}
}

if(possiblyInferrableTypes.IsSupersetOf(method.GetGenericArguments())) {
var genericMethodDefinition = method.GetGenericMethodDefinition();
var relevantBindingFlagsForOverloads =
BindingFlags.Public
| (!method.IsPublic ? BindingFlags.NonPublic : 0)
| (method.IsStatic ? BindingFlags.Static : BindingFlags.Instance)
;

var confusibleOverloads = method.DeclaringType.GetMethods(relevantBindingFlagsForOverloads)
.Where(
otherMethod =>
otherMethod != genericMethodDefinition
&& otherMethod.Name == method.Name
&& otherMethod.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(method.GetParameters().Select(pi => pi.ParameterType))
);

if(!confusibleOverloads.Any()
&& genericMethodDefinition.GetGenericArguments()
.All(typeParameter => genericMethodDefinition.GetParameters().Any(parameter => ContainsInferableType(parameter.ParameterType, typeParameter)))) {
return "";
}
}
Expand All @@ -333,16 +335,11 @@ string CreateGenericArgumentsIfNecessary(MethodCallExpression mce, MethodInfo me
return string.Concat("<", string.Join(", ", methodTypeArgs), ">");
}

static bool PopFromList<T>(List<T> list, out T val)
static bool ContainsInferableType(Type haystack, Type needle)
{
//O(1)
if(list.Count == 0) {
val = default(T);
return false;
}
val = list[list.Count - 1];
list.RemoveAt(list.Count - 1);
return true;
return haystack == needle
|| (haystack.IsArray || haystack.IsByRef) && ContainsInferableType(haystack.GetElementType(), needle)
|| haystack.IsGenericType && haystack.GetGenericArguments().Any(argType => ContainsInferableType(argType, needle));
}

public void DispatchIndex(Expression e)
Expand Down
21 changes: 14 additions & 7 deletions ExpressionToCodeTest/GenericsTestClasses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
namespace ExpressionToCodeTest {
[TestFixture]
public class TestGenerics {
[Test, Ignore("issue 13")]
[Test]
public void TypeParameters() {
Assert.AreEqual(1337, StaticTestClass.Consume(12));
Assert.AreEqual(42, StaticTestClass.Consume<int>(12));
Expand Down Expand Up @@ -49,7 +49,7 @@ public void TypeParameters2() {
); //should remove type parameters where they can be inferred.
}

[Test, Ignore("issue 13")]
[Test]
public void TypeParameters3() {
Assert.AreEqual(
@"() => new[] { 1, 2, 3 }.Cast<int>()",
Expand Down Expand Up @@ -114,15 +114,15 @@ public void StraightforwardInference() {
);
}

[Test, Ignore("issue 13")]
[Test]
public void CannotInferOneParam() {
Assert.AreEqual(
@"() => StaticTestClass.IsType<int, int>(3)",
ExpressionToCode.ToCode(() => StaticTestClass.IsType<int, int>(3))
);
}

[Test, Ignore("issue 13")]
[Test]
public void CannotInferWithoutTParam() {
Assert.AreEqual(
@"() => StaticTestClass.TEqualsInt<int>(3)",
Expand Down Expand Up @@ -192,11 +192,18 @@ public void CanInferIndirect() {
}

[Test]
public void GenericMethodCall_WhenSomeNotInferredTypeArguments_ShouldExplicitlySpecifyTypeArguments()
{
public void GenericMethodCall_WhenSomeNotInferredTypeArguments_ShouldExplicitlySpecifyTypeArguments() {
Assert.AreEqual(
@"() => StaticTestClass.IsType<int, int>(3)",
ExpressionStringify.With(explicitMethodTypeArgs: true).ToCode(() => StaticTestClass.IsType<int, int>(3))
);
}

[Test]
public void GenericMethodCall_ShouldExplicitlySpecifyTypeArguments() {
Assert.AreEqual(
"() => MakeMe<Cake, string>(() => new Cake())",
ExpressionStringify.With(explicitMethodTypeArgs: true).ToCode(() => MakeMe<Cake, string>(() => new Cake())));
ExpressionToCode.ToCode(() => MakeMe<Cake, string>(() => new Cake())));
}

T MakeMe<T, TNotInferredFromArgument>(Func<T> maker) { return maker(); }
Expand Down

0 comments on commit 49f65ad

Please sign in to comment.