Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added: IExpressionToCode interface with ToCode methods and full range of... #40

Closed
wants to merge 1 commit into from
Closed
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
13 changes: 9 additions & 4 deletions ExpressionToCodeLib/CSharpFriendlyTypeName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

namespace ExpressionToCodeLib {
static class CSharpFriendlyTypeName {
public static string Get(Type type) { return GenericTypeName(type) ?? ArrayTypeName(type) ?? AliasName(type) ?? NormalName(type); }
public static string Get(Type type, bool fullName = false) {
return GenericTypeName(type, fullName) ?? ArrayTypeName(type) ?? AliasName(type) ?? NormalName(type, fullName);
}

static string AliasName(Type type) {
if (type == typeof(bool)) {
Expand Down Expand Up @@ -43,9 +45,12 @@ static string AliasName(Type type) {
}
}

static string NormalName(Type type) { return (type.DeclaringType == null || type.IsGenericParameter ? "" : Get(type.DeclaringType) + ".") + type.Name; }
static string NormalName(Type type, bool fullName = false) {
return (type.DeclaringType == null || type.IsGenericParameter ? "" : Get(type.DeclaringType) + ".")
+ (fullName ? type.FullName ?? type.Name : type.Name);
}

static string GenericTypeName(Type type) {
static string GenericTypeName(Type type, bool fullName = false) {
if (!type.IsGenericType) {
return null;
}
Expand All @@ -60,7 +65,7 @@ static string GenericTypeName(Type type) {
var revNestedTypeNames = new List<string>();

while (type != null) {
var name = type.Name;
var name = fullName ? type.FullName ?? type.Name : type.Name;
var backtickIdx = name.IndexOf('`');
if (backtickIdx < 0) {
revNestedTypeNames.Add(name);
Expand Down
45 changes: 42 additions & 3 deletions ExpressionToCodeLib/ExpressionToCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,35 @@
using System.Text;

namespace ExpressionToCodeLib {
public static class ExpressionToCode {

public interface IExpressionToCode {
string ToCode(Expression e);
}

public static class ExpressionToCodeExt {
public static string ToCode<T, T1, T2, T3>(this IExpressionToCode it, Expression<Func<T, T1, T2, T3>> e) { return it.ToCode(e); }
public static string ToCode<T, T1, T2>(this IExpressionToCode it, Expression<Func<T, T1, T2>> e) { return it.ToCode(e); }
public static string ToCode<T, T1>(this IExpressionToCode it, Expression<Func<T, T1>> e) { return it.ToCode(e); }
public static string ToCode<T>(this IExpressionToCode it, Expression<Func<T>> e) { return it.ToCode(e); }
}

public sealed class Rules {
public static readonly Rules Default = new Rules();

public bool ExplicitMethodTypeArgs { get; private set; }
public Rules WithExplicitMethodTypeArgs() { return new Rules(this) { ExplicitMethodTypeArgs = true }; }

public bool FullTypeNames { get; private set; }
public Rules WithFullTypeNames() { return new Rules(this) { FullTypeNames = true }; }

Rules() { }
Rules(Rules original) {
ExplicitMethodTypeArgs = original.ExplicitMethodTypeArgs;
FullTypeNames = original.FullTypeNames;
}
}

public sealed class ExpressionToCode : IExpressionToCode {
public static string ToCode<T, T1, T2, T3>(Expression<Func<T, T1, T2, T3>> e) { return ToCode((Expression)e); }
public static string ToCode<T, T1, T2>(Expression<Func<T, T1, T2>> e) { return ToCode((Expression)e); }
public static string ToCode<T, T1>(Expression<Func<T, T1>> e) { return ToCode((Expression)e); }
Expand All @@ -16,17 +44,28 @@ public static class ExpressionToCode {
public static string AnnotatedToCode<T, T1>(Expression<Func<T, T1>> e) { return AnnotatedToCode((Expression)e); }
public static string AnnotatedToCode<T>(Expression<Func<T>> e) { return AnnotatedToCode((Expression)e); }

public static string ToCode(Expression e) {
public static readonly IExpressionToCode Default = new ExpressionToCode(Rules.Default);

readonly Rules rules;
private ExpressionToCode(Rules rules) { this.rules = rules; }

public static IExpressionToCode With(Func<Rules, Rules> configure) {
return new ExpressionToCode(configure(Rules.Default));
}

string IExpressionToCode.ToCode(Expression e) {
StringBuilder sb = new StringBuilder();
bool ignoreInitialSpace = true;
new ExpressionToCodeImpl(
new ExpressionToCodeImpl(rules,
(etp, depth) => {
sb.Append(ignoreInitialSpace ? etp.Text.TrimStart() : etp.Text);
ignoreInitialSpace = etp.Text.Any() && ShouldIgnoreSpaceAfter(etp.Text[etp.Text.Length - 1]);
}).ExpressionDispatch(e);
return sb.ToString();
}

public static string ToCode(Expression e) { return Default.ToCode(e); }

public static string AnnotatedToCode(Expression expr) { return AnnotatedToCode(expr, null, false); }

internal static string AnnotatedToCode(Expression expr, string msg, bool ignoreOutermostValue) {
Expand Down
44 changes: 28 additions & 16 deletions ExpressionToCodeLib/ExpressionToCodeImpl.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
Expand All @@ -9,10 +8,18 @@
namespace ExpressionToCodeLib {
class ExpressionToCodeImpl : IExpressionTypeDispatch {
#region General Helpers
readonly Rules rules;
readonly Action<ExprTextPart, int> sink;
readonly Func<Type, string> GetCSharpFriendlyTypeName = type => CSharpFriendlyTypeName.Get(type);
int Depth;
//TODO: refactor IExpressionTypeDispatch into an input/output model to avoid this tricky side-effect approach.
internal ExpressionToCodeImpl(Action<ExprTextPart, int> sink) { this.sink = sink; }
internal ExpressionToCodeImpl(Rules rules, Action<ExprTextPart, int> sink) {
rules = rules ?? Rules.Default;
if (rules.FullTypeNames) GetCSharpFriendlyTypeName = type => CSharpFriendlyTypeName.Get(type, true);
this.rules = rules;
this.sink = sink;
}
internal ExpressionToCodeImpl(Action<ExprTextPart, int> sink) : this(Rules.Default, sink) { }
void Sink(string text) { sink(ExprTextPart.TextOnly(text), Depth); }
void Sink(string text, Expression value) { sink(ExprTextPart.TextAndExpr(text, value), Depth); }

Expand Down Expand Up @@ -138,9 +145,9 @@ void UnaryDispatchConvert(Expression e) {
var ue = (UnaryExpression)e;
if (e.Type.IsAssignableFrom(ue.Operand.Type)) // base class, basically; don't re-print identical values.
{
Sink("(" + CSharpFriendlyTypeName.Get(e.Type) + ")");
Sink("(" + GetCSharpFriendlyTypeName(e.Type) + ")");
} else {
Sink("(" + CSharpFriendlyTypeName.Get(e.Type) + ")", e);
Sink("(" + GetCSharpFriendlyTypeName(e.Type) + ")", e);
}
NestExpression(ue.NodeType, ue.Operand);
}
Expand All @@ -154,7 +161,7 @@ void UnaryPostfixDispatch(string op, Expression e) {
void TypeOpDispatch(string op, Expression e) {
NestExpression(e.NodeType, ((TypeBinaryExpression)e).Expression);
Sink(" " + op + " ", e);
Sink(CSharpFriendlyTypeName.Get(((TypeBinaryExpression)e).TypeOperand));
Sink(GetCSharpFriendlyTypeName(((TypeBinaryExpression)e).TypeOperand));
}
#endregion

Expand Down Expand Up @@ -190,7 +197,7 @@ public void DispatchMemberAccess(Expression e) {
NestExpression(e.NodeType, memberOfExpr);
Sink(".");
} else if (ReflectionHelpers.IsMemberInfoStatic(me.Member)) {
Sink(CSharpFriendlyTypeName.Get(me.Member.ReflectedType) + ".");
Sink(GetCSharpFriendlyTypeName(me.Member.ReflectedType) + ".");
}

Sink(me.Member.Name, e);
Expand Down Expand Up @@ -248,9 +255,14 @@ void SinkMethodName(MethodCallExpression mce, MethodInfo method, Expression objE
Sink(".");
}
} else if (method.IsStatic) {
Sink(CSharpFriendlyTypeName.Get(method.DeclaringType) + "."); //TODO:better reference avoiding for this?
Sink(GetCSharpFriendlyTypeName(method.DeclaringType) + "."); //TODO:better reference avoiding for this?
}
var methodName = method.Name;
if (rules.ExplicitMethodTypeArgs && method.IsGenericMethod) {
var methodTypeArgs = method.GetGenericArguments().Select(GetCSharpFriendlyTypeName).ToArray();
methodName += string.Concat("<", string.Join(", ", methodTypeArgs), ">");
}
Sink(method.Name, mce);
Sink(methodName, mce);
}

public void DispatchIndex(Expression e) {
Expand All @@ -267,7 +279,7 @@ public void DispatchIndex(Expression e) {
public void DispatchInvoke(Expression e) {
var ie = (InvocationExpression)e;
if (ie.Expression.NodeType == ExpressionType.Lambda) {
Sink("new " + CSharpFriendlyTypeName.Get(ie.Expression.Type));
Sink("new " + GetCSharpFriendlyTypeName(ie.Expression.Type));
}
NestExpression(ie.NodeType, ie.Expression);
var invokeMethod = ie.Expression.Type.GetMethod("Invoke");
Expand Down Expand Up @@ -307,7 +319,7 @@ public void DispatchConditional(Expression e) {
public void DispatchListInit(Expression e) {
var lie = (ListInitExpression)e;
Sink("new ", lie);
Sink(CSharpFriendlyTypeName.Get(lie.NewExpression.Constructor.ReflectedType));
Sink(GetCSharpFriendlyTypeName(lie.NewExpression.Constructor.ReflectedType));
if (lie.NewExpression.Arguments.Any()) {
ArgListDispatch(GetArgumentsForMethod(lie.NewExpression.Constructor, lie.NewExpression.Arguments));
}
Expand Down Expand Up @@ -347,7 +359,7 @@ void DispatchMemberBinding(MemberBinding mb) {
public void DispatchMemberInit(Expression e) {
var mie = (MemberInitExpression)e;
Sink("new ", mie);
Sink(CSharpFriendlyTypeName.Get(mie.NewExpression.Constructor.ReflectedType));
Sink(GetCSharpFriendlyTypeName(mie.NewExpression.Constructor.ReflectedType));
if (mie.NewExpression.Arguments.Any()) {
ArgListDispatch(GetArgumentsForMethod(mie.NewExpression.Constructor, mie.NewExpression.Arguments));
}
Expand Down Expand Up @@ -382,7 +394,7 @@ public void DispatchNew(Expression e) {
}
Sink(" }");
} else {
Sink("new " + CSharpFriendlyTypeName.Get(ne.Type), ne);
Sink("new " + GetCSharpFriendlyTypeName(ne.Type), ne);
ArgListDispatch(GetArgumentsForMethod(ne.Constructor, ne.Arguments));
}
//TODO: deal with anonymous types.
Expand All @@ -394,14 +406,14 @@ public void DispatchNewArrayInit(Expression e) {
bool isDelegate = typeof(Delegate).IsAssignableFrom(arrayElemType);
bool implicitTypeOK = !isDelegate && nae.Expressions.Any()
&& nae.Expressions.All(expr => expr.Type == arrayElemType);
Sink("new" + (implicitTypeOK ? "" : " " + CSharpFriendlyTypeName.Get(arrayElemType)) + "[] ", nae);
Sink("new" + (implicitTypeOK ? "" : " " + GetCSharpFriendlyTypeName(arrayElemType)) + "[] ", nae);
ArgListDispatch(nae.Expressions.Select(e1 => new Argument { Expr = e1 }), null, "{ ", " }");
}

public void DispatchNewArrayBounds(Expression e) {
var nae = (NewArrayExpression)e;
Type arrayElemType = nae.Type.GetElementType();
Sink("new " + CSharpFriendlyTypeName.Get(arrayElemType), nae);
Sink("new " + GetCSharpFriendlyTypeName(arrayElemType), nae);
ArgListDispatch(nae.Expressions.Select(e1 => new Argument { Expr = e1 }), null, "[", "]");
}
#endregion
Expand Down Expand Up @@ -463,7 +475,7 @@ public void DispatchParameter(Expression e) {
public void DispatchRightShift(Expression e) { BinaryDispatch(">>", e); }
public void DispatchSubtract(Expression e) { BinaryDispatch("-", e); }
public void DispatchSubtractChecked(Expression e) { BinaryDispatch("-", e); }
public void DispatchTypeAs(Expression e) { UnaryPostfixDispatch(" as " + CSharpFriendlyTypeName.Get(e.Type), e); }
public void DispatchTypeAs(Expression e) { UnaryPostfixDispatch(" as " + GetCSharpFriendlyTypeName(e.Type), e); }
public void DispatchTypeIs(Expression e) { TypeOpDispatch("is", e); }
public void DispatchAssign(Expression e) { BinaryDispatch("=", e); }
public void DispatchDecrement(Expression e) { UnaryPostfixDispatch(" - 1", e); }
Expand Down Expand Up @@ -497,7 +509,7 @@ public void DispatchParameter(Expression e) {
public void DispatchDefault(Expression e) {
var defExpr = (DefaultExpression)e;

Sink("default(" + CSharpFriendlyTypeName.Get(defExpr.Type) + ")");
Sink("default(" + GetCSharpFriendlyTypeName(defExpr.Type) + ")");
}

public void DispatchExtension(Expression e) { throw new NotImplementedException(); }
Expand Down
8 changes: 8 additions & 0 deletions ExpressionToCodeTest/ExpressionToCodeLibTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,14 @@ public void LambdaInvocation_CustomDelegate()
"() => new CustomDelegate(n => n + 1)(1)",
ExpressionToCode.ToCode(() => new CustomDelegate(n => n + 1)(1)));
}

[Test]
public void FullTypeName_IfCorrespondingRuleSpecified()
{
Assert.AreEqual(
"() => new ExpressionToCodeTest.ClassA()",
ExpressionToCode.With(rules => rules.WithFullTypeNames()).ToCode(() => new ClassA()));
}
}

public delegate int DelegateWithRefAndOut(ref int someVar, out int anotherVar);
Expand Down
12 changes: 12 additions & 0 deletions ExpressionToCodeTest/GenericsTestClasses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,20 @@ public void CanInferIndirect() {
ExpressionToCode.ToCode(() => GenericClass<int>.IsFunc2OfType((int x) => x))
);
}

[Test]
public void GenericMethodCall_WhenSomeNotInferredTypeArguments_ShouldExplicitlySpecifyTypeArguments()
{
Assert.AreEqual(
"() => MakeMe<Cake, string>(() => new Cake())",
ExpressionToCode.With(rules => rules.WithExplicitMethodTypeArgs()).ToCode(() => MakeMe<Cake, string>(() => new Cake())));
}

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

internal class Cake { }

class GenericClass<T> {
T val;
public GenericClass(T pVal) { val = pVal; }
Expand Down