<|start_filename|>ExpressionToCodeTest/TopLevelProgramTest.cs<|end_filename|>
﻿using System;
using TopLevelProgramExample;
using Xunit;
using static TopLevelProgramExample.TopLevelProgramMarker;

namespace ExpressionToCodeTest
{
    public class TopLevelProgramTest
    {
        [Fact]
        public void CanRunTopLevelProgram()
        {
            LambdaInsideLocalFunction = LambdaToMyVar = LambdaInsideNestedClassMethod = null;

            var topLevelProgram = (Action<string[]>)Delegate.CreateDelegate(typeof(Action<string[]>), typeof(TopLevelProgramMarker).Assembly.EntryPoint);
            topLevelProgram(new[] { "test" });

            Assert.Equal("() => myVariable", LambdaToMyVar);
            Assert.Equal("() => InnerClass.StaticInt + InstanceInt", LambdaInsideNestedClassMethod);
            Assert.Equal("() => inLocalFunction + myVariable.Length - arg - withImplicitType.A.Length * 27", LambdaInsideLocalFunction);
        }
    }
}


<|start_filename|>ExpressionToCodeLib/ObjectStringify.cs<|end_filename|>
﻿using System;
using System.Linq;
using ExpressionToCodeLib.Internal;

namespace ExpressionToCodeLib
{
    public static class ObjectStringify
    {
        public static readonly IObjectStringifier Default = new ObjectStringifyImpl();
        public static readonly IObjectStringifier WithFullTypeNames = new ObjectStringifyImpl(true);
    }
}


<|start_filename|>ExpressionToCodeLib/Internal/ExpressionToCodeString.cs<|end_filename|>
﻿using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace ExpressionToCodeLib.Internal
{
    sealed class ExpressionToCodeString
    {
        public static string ToCodeString(ExpressionToCodeConfiguration config, Expression e)
        {
            var sb = new StringBuilder();
            var ignoreInitialSpace = true;
            var stringifiedExpr = new ExpressionToCodeImpl(config).ExpressionDispatch(e);
            AppendTo(sb, ref ignoreInitialSpace, stringifiedExpr);
            return sb.ToString();
        }

        static void AppendTo(StringBuilder sb, ref bool ignoreInitialSpace, StringifiedExpression node)
        {
            if (node.Text != null) {
                sb.Append(ignoreInitialSpace ? node.Text.TrimStart() : node.Text);
                ignoreInitialSpace = node.Text.Any() && ExpressionToCode.ShouldIgnoreSpaceAfter(node.Text[node.Text.Length - 1]);
            } else {
                foreach (var kid in node.Children) {
                    AppendTo(sb, ref ignoreInitialSpace, kid);
                }
            }
        }
    }
}


<|start_filename|>ExpressionToCodeLib/CodeAnnotators.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using ExpressionToCodeLib.Internal;

namespace ExpressionToCodeLib
{
    public static class CodeAnnotators
    {
        /// <summary>
        ///     This code annotator places sub-expression values below the stringified expression and connects the value to the
        ///     appropriate
        ///     place in the expression with an ascii-art line. It works reasonably well for short expression, but large
        ///     expressions are unwieldy and hard to read.
        ///     This works best in monospace fonts.
        /// </summary>
        public static readonly ICodeAnnotator ValuesOnStalksCodeAnnotator = new ValuesOnStalksCodeAnnotator();

        /// <summary>
        ///     This code annotator summarizes sub-expression values on separate lines below the stringified expression.
        ///     This works reasonably well even for large expressions because the sub-expressions are represented compactly and
        ///     there's no need for overal alignment.
        /// </summary>
        public static readonly ICodeAnnotator SubExpressionPerLineCodeAnnotator = new SubExpressionPerLineCodeAnnotator();
    }
}


<|start_filename|>ExpressionToCodeTest/ExpressionToCodeLibTest.cs<|end_filename|>
﻿using System.Reflection;
using Xunit;
// ReSharper disable RedundantEnumerableCastCall
// ReSharper disable RedundantNameQualifier
// ReSharper disable ConvertToConstant.Local
// ReSharper disable RedundantLogicalConditionalExpressionOperand
// ReSharper disable RedundantCast
// ReSharper disable ConstantNullCoalescingCondition
// ReSharper disable EqualExpressionComparison
// ReSharper disable RedundantToStringCall
#pragma warning disable 1720
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Xml;
using ExpressionToCodeLib;

namespace ExpressionToCodeTest
{
    public sealed class ExpressionToCodeLibTest
    {
        [Fact]
        public void ComplexObjectToCodeAlsoSupportsExpressions()
            => Assert.Equal("() => 42", ObjectToCode.ComplexObjectToPseudoCode((Expression<Func<int>>)(() => 42)));

        [Fact]
        public void AddOperator()
        {
            var x = 0;
            Assert.Equal(
                @"() => 1 + x + 2 == 4",
                ExpressionToCode.ToCode(() => 1 + x + 2 == 4));
        }

        [Fact]
        public void AnonymousClasses()
            => Assert.Equal(
                @"() => new { X = 3, A = ""a"" } == new { X = 3, A = ""a"" }",
                ExpressionToCode.ToCode(() => new { X = 3, A = "a" } == new { X = 3, A = "a" }));

        [Fact]
        public void ArrayIndex()
            => Assert.Equal(
                @"() => new[] { 3, 4, 5 }[0 + (int)(DateTime.Now.Ticks % 3L)] == 3",
                ExpressionToCode.ToCode(() => new[] { 3, 4, 5 }[0 + (int)(DateTime.Now.Ticks % 3)] == 3));

        [Fact]
        public void ArrayLengthAndDoubles()
            => Assert.Equal(
                @"() => new[] { 1.0, 2.01, 3.5 }.Concat(new[] { 1.0, 2.0 }).ToArray().Length == 0",
                ExpressionToCode.ToCode(() => new[] { 1.0, 2.01, 3.5 }.Concat(new[] { 1.0, 2.0 }).ToArray().Length == 0));

        [Fact]
        public void AsOperator()
            => Assert.Equal(
                @"() => new object() as string == default(string)",
                // ReSharper disable once TryCastAndCheckForNull.0
                // ReSharper disable once SafeCastIsUsedAsTypeCheck
                ExpressionToCode.ToCode(() => new object() as string == null));

        [Fact]
        public void ComplexGenericName()
            => Assert.Equal(
                @"() => ((Func<int, bool>)(x => x > 0))(0)",
                ExpressionToCode.ToCode(() => ((Func<int, bool>)(x => x > 0))(0)));

        [Fact]
        public void DefaultValue()
            => Assert.Equal(
                @"() => new TimeSpan(1, 2, 3) == default(TimeSpan)",
                ExpressionToCode.ToCode(() => new TimeSpan(1, 2, 3) == default(TimeSpan)));

        [Fact]
        public void IndexerAccess()
        {
            var dict = Enumerable.Range(1, 20).ToDictionary(n => n.ToString());
            Assert.Equal(
                @"() => dict[""3""] == 3",
                ExpressionToCode.ToCode(() => dict["3"] == 3));
        }

        [Fact]
        public void IsOperator()
            => Assert.Equal(
                @"() => new object() is string",
                ExpressionToCode.ToCode(() => new object() is string));

        [Fact]
        public void ArrayOfFuncInitializer()
            => Assert.Equal(
                @"() => new Func<int>[] { () => 1, () => 2 }",
                ExpressionToCode.ToCode(() => new Func<int>[] { () => 1, () => 2 }));

        [Fact]
        public void ArrayOfFuncInitializer_FullNames()
            => Assert.Equal(
                @"() => new System.Func<int>[] { () => 1, () => 2 }",
                ExpressionToCodeConfiguration.DefaultCodeGenConfiguration.WithObjectStringifier(ObjectStringify.WithFullTypeNames)
                    .GetExpressionToCode()
                    .ToCode(() => new Func<int>[] { () => 1, () => 2 }));

        [Fact]
        public void ListInitializer()
            => Assert.Equal(
                @"() => new Dictionary<int, int> { { 1, 1 }, { 2, 2 }, { 3, 4 } }.Count == 3",
                ExpressionToCode.ToCode(() => new Dictionary<int, int> { { 1, 1 }, { 2, 2 }, { 3, 4 } }.Count == 3));

        [Fact]
        public void ListInitializer2()
            => Assert.Equal(
                @"() => new List<int>(50) { 1, 2, 3 }.Count == 3",
                ExpressionToCode.ToCode(() => new List<int>(50) { 1, 2, 3 }.Count == 3));

        [Fact]
        public void ListInitializer3()
            => Assert.Equal(
                @"() => new List<int> { 1, 2, 3 }.Count == 3",
                ExpressionToCode.ToCode(() => new List<int> { 1, 2, 3 }.Count == 3));

        [Fact]
        public void LiteralCharAndProperty()
            => Assert.Equal(
                @"() => new string(' ', 3).Length == 1",
                ExpressionToCode.ToCode(() => new string(' ', 3).Length == 1));

        [Fact]
        public void MembersBuiltin()
        {
            Assert.Equal(
                @"() => 1.23m.ToString()",
                // ReSharper disable once SpecifyACultureInStringConversionExplicitly
                ExpressionToCode.ToCode(() => 1.23m.ToString()));
            Assert.Equal(
                @"() => AttributeTargets.All.HasFlag(AttributeTargets.Assembly)",
                ExpressionToCode.ToCode(() => AttributeTargets.All.HasFlag(AttributeTargets.Assembly)));
            Assert.Equal(
                @"() => ""abc"".Length == 3",
                ExpressionToCode.ToCode(() => "abc".Length == 3));
            Assert.Equal(
                @"() => 'a'.CompareTo('b') < 0",
                ExpressionToCode.ToCode(() => 'a'.CompareTo('b') < 0));
        }

        [Fact]
        public void MembersDefault()
        {
#pragma warning disable CS8602 // Dereference of a possibly null reference. - these are false positives, because the code isn't executed, it's stringified.
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - these are false positives, because the code isn't executed, it's stringified.
            Assert.Equal(
                @"() => default(DateTime).Ticks == 0L",
                ExpressionToCode.ToCode(() => default(DateTime).Ticks == 0L));
            Assert.Equal(
                @"() => default(int[]).Length == 0",
                ExpressionToCode.ToCode(() => default(int[]).Length == 0));
            Assert.Equal(
                @"() => default(Type).GetTypeInfo().IsLayoutSequential",
                ExpressionToCode.ToCode(() => default(Type).GetTypeInfo().IsLayoutSequential));
            Assert.Equal(
                @"() => default(List<int>).Count",
                ExpressionToCode.ToCode(() => default(List<int>).Count));
            Assert.Equal(
                @"() => default(int[]).Clone() == null",
                ExpressionToCode.ToCode(() => default(int[]).Clone() == null));
            //default(Type).IsInstanceOfType(new object()) is not compiled as extension method in .net core!
            Assert.Equal(
                @"() => default(IEnumerable<Type>).Any()",
                ExpressionToCode.ToCode(() => default(IEnumerable<Type>).Any()));
            Assert.Equal(
                @"() => default(List<int>).AsReadOnly()",
                ExpressionToCode.ToCode(() => default(List<int>).AsReadOnly()));
#pragma warning restore CS8602 // Dereference of a possibly null reference.
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
        }

        [Fact]
        public void MembersThis()
            => new ClassA().DoAssert();

        [Fact]
        public void MethodGroupAsExtensionMethod()
        {
            var actual = ExpressionToCode.ToCode(() => (Func<bool>)new[] { 2000, 2004, 2008, 2012 }.Any);
            Assert.Equal(
                "() => (Func<bool>)new[] { 2000, 2004, 2008, 2012 }.Any",
                actual);
        }

        [Fact]
        public void MethodGroupConstant()
        {
            Assert.Equal(
                @"() => Array.TrueForAll(new[] { 2000, 2004, 2008, 2012 }, (Predicate<int>)DateTime.IsLeapYear)",
                ExpressionToCode.ToCode(() => Array.TrueForAll(new[] { 2000, 2004, 2008, 2012 }, DateTime.IsLeapYear)));

            var set = new HashSet<int>();
            Assert.Equal(
                @"() => new[] { 2000, 2004, 2008, 2012 }.All((Func<int, bool>)set.Add)",
                ExpressionToCode.ToCode(() => new[] { 2000, 2004, 2008, 2012 }.All(set.Add)));

            Func<Func<object?, object?, bool>, bool> sink = f => f(null, null);
            Assert.Equal(
                @"() => sink((Func<object, object, bool>)object.Equals)",
                ExpressionToCode.ToCode(() => sink(int.Equals)));
        }

        [Fact]
        public void MultipleCasts()
            => Assert.Equal(
                @"() => 1 == (int)(object)1",
                ExpressionToCode.ToCode(() => 1 == (int)(object)1));

        [Fact]
        public void MultipleDots()
            => Assert.Equal(
                @"() => 3.ToString().ToString().Length > 0",
                ExpressionToCode.ToCode(() => 3.ToString().ToString().Length > 0));

        [Fact]
        public void NestedLambda_NoParameters()
        {
            Func<Func<int>, int> call = f => f();
            Assert.Equal(
                @"() => call(() => 42)",
                ExpressionToCode.ToCode(() => call(() => 42))
            ); //no params
        }

        [Fact]
        public void NestedLambda_OneParameter()
        {
            Assert.Equal(
                @"() => new[] { 37, 42 }.Select(x => x * 2)",
                ExpressionToCode.ToCode(() => new[] { 37, 42 }.Select(x => x * 2))
            );
            Assert.Equal(
                @"() => Buzz(x => true)",
                ExpressionToCode.ToCode(() => Buzz(x => true))
            );
        }

        [Fact]
        public void NestedLambda_TwoParameters()
            => Assert.Equal(
                @"() => new[] { 37, 42 }.Select((x, i) => x * 2)",
                ExpressionToCode.ToCode(() => new[] { 37, 42 }.Select((x, i) => x * 2))
            );

        // ReSharper disable once MemberCanBeMadeStatic.Local
        bool Fizz(Func<int, bool> a)
            => a(42);

        // ReSharper disable once MemberCanBeMadeStatic.Local
        bool Buzz(Func<int, bool> a)
            => a(42);

        // ReSharper disable once MemberCanBeMadeStatic.Local
        bool Fizz(Func<string, bool> a)
            => a("42");

        [Fact]
        public void NestedLambda_MultipleOverloads()
        {
            Assert.Equal(
                @"() => Fizz(x => x == ""a"")",
                ExpressionToCode.ToCode(() => Fizz(x => x == "a"))
            );
            Assert.Equal(
                @"() => Fizz(x => x == 37)",
                ExpressionToCode.ToCode(() => Fizz(x => x == 37))
            );
        }

        [Fact(Skip = "issue 14")]
        public void NestedLambda_UncertainOverload()
            => Assert.Equal(
                @"() => Fizz((int x) => true)",
                ExpressionToCode.ToCode(() => Fizz((int x) => true))
            );

        [Fact]
        public void NewArrayAndExtensionMethod()
            => Assert.Equal(
                @"() => new[] { 1.0, 2.01, 3.5 }.SequenceEqual(new[] { 1.0, 2.01, 3.5 })",
                ExpressionToCode.ToCode(() => new[] { 1.0, 2.01, 3.5 }.SequenceEqual(new[] { 1.0, 2.01, 3.5 })));

        [Fact]
        public void NewMultiDimArray()
            => Assert.Equal(
                @"() => new int[3, 4].Length == 1",
                ExpressionToCode.ToCode(() => new int[3, 4].Length == 1));

        [Fact]
        public void NewObject()
            => Assert.Equal(
                @"() => new object()",
                ExpressionToCode.ToCode(() => new object()));

        [Fact]
        public void NewObjectNotEqualsNewObject()
            => Assert.Equal(
                @"() => new object() != new object()",
                ExpressionToCode.ToCode(() => new object() != new object()));

        [Fact]
        public void NotOperator()
        {
            var x = true;
            var y = 3;
            byte z = 42;
            Assert.Equal(
                @"() => ~z == 0",
                ExpressionToCode.ToCode(() => ~(int)z == 0));
            Assert.Equal(
                @"() => ~y == 0",
                ExpressionToCode.ToCode(() => ~y == 0));
            Assert.Equal(
                @"() => !x",
                ExpressionToCode.ToCode(() => !x));
        }

        [Fact]
        public void ObjectInitializers()
        {
            var s = new XmlReaderSettings {
                CloseInput = false,
                CheckCharacters = false
            };
            Assert.Equal(
                @"() => new XmlReaderSettings { CloseInput = s.CloseInput, CheckCharacters = s.CheckCharacters }.Equals(s)",
                ExpressionToCode.ToCode(
                    () => new XmlReaderSettings { CloseInput = s.CloseInput, CheckCharacters = s.CheckCharacters }.Equals(s)));
        }

        [Fact]
        public void Quoted()
            => Assert.Equal(
                @"() => (Expression<Func<int, string, string>>)((n, s) => s + n.ToString()) != null",
                ExpressionToCode.ToCode(() => (Expression<Func<int, string, string>>)((n, s) => s + n.ToString()) != null));

        [Fact]
        public void Quoted2()
            => Assert.Equal(
                @"() => ExpressionToCode.ToCode(() => true).Length > 5",
                ExpressionToCode.ToCode(() => ExpressionToCode.ToCode(() => true).Length > 5));

        [Fact]
        public void QuotedWithAnonymous()
            => Assert.Equal(
                @"() => new[] { new { X = ""a"", Y = ""b"" } }.Select(o => o.X + o.Y).Single()",
                ExpressionToCode.ToCode(() => new[] { new { X = "a", Y = "b" } }.Select(o => o.X + o.Y).Single()));

        [Fact]
        public void StaticCall()
            => Assert.Equal(
                @"() => object.Equals(3, 0)",
                ExpressionToCode.ToCode(() => Equals(3, 0)));

        [Fact]
        public void ThisCall()
            => Assert.Equal(
                @"() => !Equals(3)",
                ExpressionToCode.ToCode(() => !Equals(3)));

        [Fact]
        public void ThisExplicit()
            => Assert.Equal(
                @"() => object.Equals(this, 3)",
                // ReSharper disable once SuspiciousTypeConversion.Global
                ExpressionToCode.ToCode(() => Equals(this, 3)));

        [Fact]
        public void TypedConstant()
            => Assert.Equal(
                @"() => new[] { typeof(int), typeof(string) }",
                ExpressionToCode.ToCode(() => new[] { typeof(int), typeof(string) }));

        [Fact]
        public void StaticMembers()
            => Assert.Equal(
                @"() => (DateTime.Now > DateTime.Now + TimeSpan.FromMilliseconds(10.001)).ToString() == ""False""",
                ExpressionToCode.ToCode(
                    () => (DateTime.Now > DateTime.Now + TimeSpan.FromMilliseconds(10.001)).ToString() == "False"));

        [Fact]
        public void Strings2()
        {
            var x = "X";
            const string y = "Y";
            Assert.Equal(
                @"() => x != ""Y"" && x.Length == ""Y"".Length && ""a"".Length == 1",
                ExpressionToCode.ToCode(() => x != y && x.Length == y.Length && "a".Length == 1));
        }

        [Fact]
        public void StringAccessor()
            => Assert.Equal(
                @"() => ""abc""[1] == 'b'",
                ExpressionToCode.ToCode(() => "abc"[1] == 'b'));

        [Fact]
        public void StringConcat()
        {
            var x = "X";
            Assert.Equal(
                @"() => ((""a\n\\b"" ?? x) + x).Length == 2 ? false : true",
                // ReSharper disable once SimplifyConditionalTernaryExpression
#pragma warning disable 162
                ExpressionToCode.ToCode(() => (("a\n\\b" ?? x) + x).Length == 2 ? false : true));
#pragma warning restore 162
        }

        [Fact]
        public void ArgumentWithRefModifier()
        {
            var x = "a";
            Assert.Equal(
                @"() => MethodWithRefParam(ref x)",
                ExpressionToCode.ToCode(() => MethodWithRefParam(ref x)));
        }

        // ReSharper disable once MemberCanBeMadeStatic.Local
        T MethodWithRefParam<T>(ref T input)
            => input;

        [Fact]
        public void ArgumentWithOutModifier()
        {
            var x = "a";
            string y;
            Assert.Equal(
                @"() => MethodWithOutParam(ref x, out y)",
                ExpressionToCode.ToCode(() => MethodWithOutParam(ref x, out y)));
        }

        // ReSharper disable once MemberCanBeMadeStatic.Local
        T MethodWithOutParam<T>(ref T input, out T output)
            => output = input;

        [Fact]
        public void StaticMethodWithRefAndOutModifiers()
        {
            var x = "a";
            object? y;
            Assert.Equal(
                @"() => ClassA.MethodWithOutAndRefParam(ref x, out y, 3)",
                ExpressionToCode.ToCode(() => ClassA.MethodWithOutAndRefParam(ref x, out y, 3)));
        }

        [Fact]
        public void ConstructorMethodWithRefAndOutModifiers()
        {
            var x = 42;
            int y;
            Assert.Equal(
                @"() => new ClassA(ref x, out y)",
                ExpressionToCode.ToCode(() => new ClassA(ref x, out y)));
        }

        [Fact]
        public void ExtensionMethodWithRefAndOutModifiers()
        {
            var x = 42;
            long y;
            Assert.Equal(
                @"() => DateTime.Now.AnExtensionMethod(ref x, 5, out y)",
                ExpressionToCode.ToCode(() => DateTime.Now.AnExtensionMethod(ref x, 5, out y)));
        }

        [Fact]
        public void DelegateCallWithRefAndOutModifiers()
        {
            var x = 42;
            int y;
            DelegateWithRefAndOut myDelegate = (ref int someVar, out int anotherVar) => anotherVar = someVar;
            Assert.Equal(
                @"() => myDelegate(ref x, out y)",
                ExpressionToCode.ToCode(() => myDelegate(ref x, out y)));
        }

        [Fact]
        public void LambdaInvocation_Func_int()
            => Assert.Equal(
                "() => new Func<int>(() => 1)()",
                ExpressionToCode.ToCode(() => new Func<int>(() => 1)()));

        [Fact]
        public void LambdaInvocation_CustomDelegate()
            => Assert.Equal(
                "() => new CustomDelegate(n => n + 1)(1)",
                ExpressionToCode.ToCode(() => new CustomDelegate(n => n + 1)(1)));

        [Fact]
        public void FullTypeName_IfCorrespondingRuleSpecified()
            => Assert.Equal(
                "() => new ExpressionToCodeTest.ClassA()",
                ExpressionToCodeConfiguration.DefaultCodeGenConfiguration.WithObjectStringifier(ObjectStringify.WithFullTypeNames)
                    .GetExpressionToCode()
                    .ToCode(() => new ClassA()));

        [Fact]
        public void FullTypeName_WhenRequestedEvenForMessyGenerics()
            => Assert.Equal(
                "() => typeof(ExpressionToCodeTest.Outer<int, int>.Nested<string>)",
                ExpressionToCodeConfiguration.DefaultCodeGenConfiguration.WithObjectStringifier(ObjectStringify.WithFullTypeNames)
                    .GetExpressionToCode()
                    .ToCode(() => typeof(Outer<int, int>.Nested<string>)));

        [Fact]
        public void PlainTypeName_WhenRequestedEvenForMessyGenerics()
            => Assert.Equal(
                "() => typeof(Outer<int, int>.Nested<string>)",
                ExpressionToCodeConfiguration.DefaultCodeGenConfiguration.GetExpressionToCode().ToCode(() => typeof(Outer<int, int>.Nested<string>)));

        [Fact]
        public void PlainTypeName_WhenFullTypeNameRequestedForUnboundTypeArgument()
        {
            var unboundListTypeArg = typeof(List<>).GetGenericArguments()[0];
            Assert.Equal(
                "T",
                ObjectStringify.WithFullTypeNames.TypeNameToCode(unboundListTypeArg));
        }

        [Fact]
        public void FullTypeName_ForNestedType()
        {
            var code = ExpressionToCodeConfiguration.DefaultCodeGenConfiguration.WithObjectStringifier(ObjectStringify.WithFullTypeNames)
                .GetExpressionToCode()
                .ToCode(() => new ExpressionToCodeTest.ExpressionToCodeLibTest.B());
            Assert.Equal("() => new ExpressionToCodeTest.ExpressionToCodeLibTest.B()", code);
        }

        sealed class B { }

        [Fact]
        public void ThisPropertyAccess()
            => Assert.Equal("() => TheProperty", ExpressionToCode.ToCode(() => TheProperty));

        [Fact]
        public void ThisProtectedPropertyAccess()
        {
            var code = ExpressionToCode.ToCode(() => TheProtectedProperty);
            Assert.Equal("() => TheProtectedProperty", code);
        }

        [Fact]
        public void ThisProtectedWithPrivateSetterPropertyAccess()
        {
            var code = ExpressionToCode.ToCode(() => TheProtectedWithPrivateSetterProperty);
            Assert.Equal("() => TheProtectedWithPrivateSetterProperty", code);
        }

        [Fact]
        public void ThePrivateStaticPropertyAccess()
        {
            var code = ExpressionToCode.ToCode(() => ThePrivateStaticProperty);
            Assert.Equal("() => ExpressionToCodeLibTest.ThePrivateStaticProperty", code);
        }

        [Fact]
        public void ThisMethodCall()
        {
            var code = ExpressionToCode.ToCode(() => ReturnZero());
            Assert.Equal("() => ReturnZero()", code);
        }

        [Fact]
        public void ThisStaticMethodCall()
        {
            var code = ExpressionToCode.ToCode(() => StaticReturnZero());
            Assert.Equal("() => ExpressionToCodeLibTest.StaticReturnZero()", code);
        }

        public int ReturnZero()
            => 0;

        public static int StaticReturnZero()
            => 0;

        [Fact]
        public void ThisIndexedProperty()
        {
            var actual = ExpressionToCode.ToCode(() => this[1]);
            Assert.Equal("() => this[1]", actual);
        }

        [Fact]
        public void DetectsClosuresInNestedClasses()
        {
            var expr = new ClassWithClosure {
                someValue = "test "
            }.GetExpression(DayOfWeek.Friday);
            Assert.Equal(@"() => someValue + closedVariable + "" "" + argument", expr);
        }

        // ReSharper disable once ClassCanBeSealed.Local
        class ClassWithClosure
        {
            public string? someValue;

            public string GetExpression(DayOfWeek argument)
            {
                var arr = new[] { 37 };
                foreach (var closedVariable in arr) {
                    return ExpressionToCode.ToCode(() => someValue + closedVariable + " " + argument);
                }

                throw new Exception();
            }
        }

        // ReSharper disable once UnusedParameter.Global
        public string this[int index]
            => "TheIndexedValue";

        public string TheProperty
            => "TheValue";

        // ReSharper disable once MemberCanBeMadeStatic.Local
        string TheProtectedProperty
            => "TheValue";

        static string ThePrivateStaticProperty
            => "TheValue";

        // ReSharper disable once UnusedAutoPropertyAccessor.Local
#pragma warning disable 628
        protected string? TheProtectedWithPrivateSetterProperty { get; private set; }
#pragma warning restore 628
    }

    public delegate int DelegateWithRefAndOut(ref int someVar, out int anotherVar);

    public delegate int CustomDelegate(int input);

    static class StaticHelperClass
    {
        public static long AnExtensionMethod(this DateTime date, ref int tickOffset, int dayOffset, out long alternateOut)
            => alternateOut = date.AddDays(dayOffset).Ticks + tickOffset;
    }

    sealed class ClassA
    {
        public static int MethodWithOutAndRefParam<T>(ref T input, out object? output, int x)
        {
            output = x == 4 ? default(object?) : input;
            return x;
        }

        int x;
        public ClassA() { }

        public ClassA(ref int something, out int output)
            => output = x = something;

        public void DoAssert()
        {
            x = 37;
            Assert.Equal(
                @"() => x != C()",
                ExpressionToCode.ToCode(() => x != C()));
            Assert.Equal(
                @"() => !object.ReferenceEquals(this, new ClassA())",
                ExpressionToCode.ToCode(() => !ReferenceEquals(this, new ClassA())));
            Assert.Equal(
                @"() => MyEquals(this) && !MyEquals(default(ClassA))",
                ExpressionToCode.ToCode(() => MyEquals(this) && !MyEquals(default)));
        }

        int C()
            => x + 5;

        bool MyEquals(ClassA? other)
            => other != null && x == other.x;
    }
}


<|start_filename|>TopLevelProgramExample/TopLevelProgram.cs<|end_filename|>
﻿using System;
using System.Linq.Expressions;
using ExpressionToCodeLib;
using TopLevelProgramExample;

const int SomeConst = 27;
var myVariable = "implicitly closed over";
var withImplicitType = new {
    A = "ImplicitTypeMember",
};
Console.WriteLine(TopLevelProgramMarker.LambdaToMyVar = ExpressionToCode.ToCode(() => myVariable));

new InnerClass().DoIt();
LocalFunction(123);

void LocalFunction(int arg)
{
    var inLocalFunction = 42;
    Expression<Func<int>> expression1 = () => inLocalFunction + myVariable.Length - arg - withImplicitType.A.Length * SomeConst;

    Console.WriteLine(TopLevelProgramMarker.LambdaInsideLocalFunction = ExpressionToCode.ToCode(expression1));
}

sealed class InnerClass
{
    public static int StaticInt = 37;
    public int InstanceInt = 12;

    public void DoIt()
        => Console.WriteLine(TopLevelProgramMarker.LambdaInsideNestedClassMethod = ExpressionToCode.ToCode(() => StaticInt + InstanceInt));
}


<|start_filename|>ExpressionToCodeTest/AnnotatedToCodeTest.cs<|end_filename|>
﻿using System;
using ExpressionToCodeLib;
using Xunit;

namespace ExpressionToCodeTest
{
    public class AnnotatedToCodeTest
    {
        [Fact]
        public void A1PlusB2()
        {
            var a = 1;
            var b = a + 1;

            var code = ExpressionToCode.AnnotatedToCode(() => a + b);

            Assert.Contains("a", code);
            Assert.Contains("+", code);
            Assert.Contains("b", code);
            Assert.Contains("1", code);
            Assert.Contains("2", code);
            Assert.Contains("3", code);
        }
    }
}


<|start_filename|>ExpressionToCodeLib/ExpressionTreeAssertion.cs<|end_filename|>
﻿using System;
using System.Linq.Expressions;

namespace ExpressionToCodeLib
{
    public static class ExpressionTreeAssertion
    {
        public static void Assert(this ExpressionToCodeConfiguration config, Expression<Func<bool>> assertion, string? msg = null)
        {
            var compiled = config.Value.ExpressionCompiler.Compile(assertion);
            bool ok;
            try {
                ok = compiled();
            } catch (Exception e) {
                throw new InvalidOperationException(config.Value.CodeAnnotator.AnnotateExpressionTree(config, assertion.Body, "evaluating assertion aborted due to exception: " + msg, true), e);
            }

            if (!ok) {
                throw new InvalidOperationException(config.Value.CodeAnnotator.AnnotateExpressionTree(config, assertion.Body, msg ?? "assertion failed", true), null);
            }
        }
    }
}


<|start_filename|>ExpressionToCodeTest/ExceptionsSerialization.cs<|end_filename|>
﻿#if binary_serialization
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using ExpressionToCodeLib;
using Xunit;

namespace ExpressionToCodeTest
{
    public class ExceptionsSerialization
    {
        [MethodImpl(MethodImplOptions.NoInlining)]
        static void IntentionallyFailingMethod()
            => PAssert.That(() => false);

        [Fact]
        public void PAssertExceptionIsSerializable()
            => AssertMethodFailsWithSerializableException(IntentionallyFailingMethod);

        static void AssertMethodFailsWithSerializableException(Action intentionallyFailingMethod)
        {
            var original = Assert.ThrowsAny<Exception>(intentionallyFailingMethod);

            var formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            var ms = new MemoryStream();
            formatter.Serialize(ms, original);
            var deserialized = formatter.Deserialize(new MemoryStream(ms.ToArray()));
            Assert.Equal(original.ToString(), deserialized.ToString());
        }
    }
}
#endif


<|start_filename|>ExpressionToCodeTest/ExpressionWithNameTest.cs<|end_filename|>
﻿using System;
using System.Linq;
using System.Linq.Expressions;
using ExpressionToCodeLib;
using Xunit;

namespace ExpressionToCodeTest
{
    public class ExpressionWithNameTest
    {
        [Fact]
        public void TheVariable_ToNameOf()
        {
            var theVariable = "theValue";
            var actual = ExpressionToCode.GetNameIn(() => theVariable);
            Assert.Equal("theVariable", actual);
        }

        [Fact]
        public void TheMethod_ToNameOf()
        {
            var x = 1;
            var y = "";
            var actual = ExpressionToCode.GetNameIn(() => TheComplexMethod(x, y));
            Assert.Equal("TheComplexMethod", actual);
        }

        [Fact]
        public void TheMethod_ToNameOf_asVariable()
        {
            Expression<Func<int, string, string>> theComplexMethod = (x, y) => TheComplexMethod(x, y);
            var actual = ExpressionToCode.GetNameIn(theComplexMethod);
            Assert.Equal("TheComplexMethod", actual);

            var full = ExpressionToCode.ToCode(theComplexMethod.Body);
            Assert.NotEqual(full, actual);
        }

        [Fact]
        public void TheMethod_ToNameOf_withValues()
        {
            var actual = ExpressionToCode.GetNameIn(() => TheComplexMethod(1, "2"));
            Assert.Equal("TheComplexMethod", actual);
        }

        [Fact]
        public void TheGenericMethod_ToNameOf()
        {
            var actual = ExpressionToCode.GetNameIn(() => TheGenericMethod<int>(2));
            Assert.Equal("TheGenericMethod", actual);
        }

        [Fact]
        public void TheProperty_ToNameOf()
        {
            var actual = ExpressionToCode.GetNameIn(() => TheProperty);
            Assert.Equal("TheProperty", actual);
        }

        [Fact]
        public void TheSimpleMethod_ToNameOf()
        {
            var actual = ExpressionToCode.GetNameIn(() => TheSimpleMethod());
            Assert.Equal("TheSimpleMethod", actual);
        }

        // ReSharper disable once MemberCanBeMadeStatic.Local
        void TheSimpleMethod() { }
        static string TheProperty => "TheValue";

        static string TheComplexMethod(int parameter1, string parameter2)
            => "TheMethod " + parameter1 + " " + parameter2;

        static string TheGenericMethod<T>(int two)
            => "Return value is " + two * two + typeof(T).Name;
    }
}


<|start_filename|>ExpressionToCodeLib/PAssert.cs<|end_filename|>
﻿using System;
using System.Linq.Expressions;

namespace ExpressionToCodeLib
{
    public static class PAssert
    {
        [Obsolete("Prefer PAssert.That: IsTrue is provided for compatibility with PowerAssert.NET")]
        public static void IsTrue(Expression<Func<bool>> assertion)
            => That(assertion);

        /// <summary>
        ///     Evaluates an assertion and throws an exception the assertion it returns false or throws an exception.
        ///     The exception includes the code of the assertion annotated with runtime values for its sub-expressions.
        ///     Identical functionality is available via Expect(()=>...); this can be accessed via "using static
        ///     ExpressionToCodeLib.ExpressionExpectations;".
        ///     If you want to change the layout of the value annotations, see
        ///     ExpressionToCodeConfiguration.GlobalAssertionConfiguration
        /// </summary>
        public static void That(Expression<Func<bool>> assertion, string? msg = null)
            => ExpressionToCodeConfiguration.GlobalAssertionConfiguration.Assert(assertion, msg);
    }
}


<|start_filename|>ExpressionToCodeTest/StringInterpolationTest.cs<|end_filename|>
﻿using System;
using ExpressionToCodeLib;
using System.Runtime.CompilerServices;
using Xunit;

namespace ExpressionToCodeTest
{
    public sealed class StringInterpolationTest
    {
        // ReSharper disable once MemberCanBeMadeStatic.Local
        FormattableString Interpolation(FormattableString str)
            => str;

        [Fact]
        public void InterpolationWithoutArgumentsIsJustAString()
            => Assert.Equal(
                @"() => ""abc""",
                ExpressionToCode.ToCode(() => $"abc"));

        [Fact]
        public void ForcedInterpolationWorks()
            => Assert.Equal(
                @"() => Interpolation($""abc"")",
                ExpressionToCode.ToCode(() => Interpolation($"abc")));

        [Fact]
        public void FormattableStringFactory_IsRenderedAsInterpolation()
            => Assert.Equal(
                @"() => Interpolation($""abc"")",
                ExpressionToCode.ToCode(
                    () => Interpolation(FormattableStringFactory.Create("abc"))
                ));

        [Fact]
        public void FormattableStringFactory_NonConstantStringIsNoInterpolation()
        {
            var s = "abc";
            Assert.Equal(
                @"() => Interpolation(FormattableStringFactory.Create(s, new object[] { }))",
                ExpressionToCode.ToCode(
                    () => Interpolation(FormattableStringFactory.Create(s))
                ));
        }

        [Fact]
        public void FormattableStringFactory_NonInlineArrayIsNoInterpolation()
        {
            var arr = new object[0];

            Assert.Equal(
                @"() => Interpolation(FormattableStringFactory.Create(""abc"", arr))",
                ExpressionToCode.ToCode(
                    () => Interpolation(FormattableStringFactory.Create("abc", arr))
                ));
        }

        [Fact]
        public void ForcedInterpolationWithOneArg()
            => Assert.Equal(
                @"() => Interpolation($""abc {3.0f}"")",
                ExpressionToCode.ToCode(() => Interpolation($"abc {3f}")));

        [Fact]
        public void ForcedInterpolationWithNestedString()
            => Assert.Equal(
                @"() => Interpolation($""abc {""def""}"")",
                ExpressionToCode.ToCode(() => Interpolation($"abc {"def"}")));

        [Fact]
        public void ForcedInterpolationWithNestedInterpolation()
            => Assert.Equal(
                @"() => Interpolation($""abc {Interpolation($""abc {""def""}"")}"")",
                ExpressionToCode.ToCode(
                    () => Interpolation($"abc {Interpolation($"abc {"def"}")}")
                )
            );

        [Fact]
        public void ForcedInterpolationWithTwoArguments()
            => Assert.Equal(
                @"() => Interpolation($""abc {3.0f} X {'a'} Y"")",
                ExpressionToCode.ToCode(() => Interpolation($"abc {3f} X {'a'} Y")));

        [Fact]
        public void ForcedInterpolationWithAdditionInArgument()
            => Assert.Equal(
                @"() => Interpolation($""abc {""abc"".Length + 3.5} Z"")",
                ExpressionToCodeConfiguration.DefaultCodeGenConfiguration.WithOmitImplicitCasts(true).ToCode(() => Interpolation($"abc {"abc".Length + 3.5} Z")));

        [Fact]
        public void ForcedInterpolationWithTernaryArgumentNeedsParens()
        {
            var aBoolean = true;

            Assert.Equal(
                @"() => Interpolation($""abc {(aBoolean ? 1 : 2)} Z"")",
                ExpressionToCode.ToCode(() => Interpolation($"abc {(aBoolean ? 1 : 2)} Z")));
        }

        [Fact]
        public void ForcedInterpolationWithNewlinesInSubExprIsLiteral()
            => Assert.Equal(
                @"() => Interpolation($@""abc {new { I = @""1
2
3
4
"", J = 1 }} Z"")",
                ExpressionToCode.ToCode(
                    () => Interpolation(
                        $@"abc {new { I = @"1
2
3
4
", J = 1 }} Z")
                ));

        [Fact]
        public void ForcedInterpolationWithFormatSpecifier()
            => Assert.Equal(
                @"() => Interpolation($""abc {DateTime.Now:somespecifier: yep!} Z"")",
                ExpressionToCode.ToCode(() => Interpolation($"abc {DateTime.Now:somespecifier: yep!} Z")));

        [Fact]
        public void ForcedInterpolationWithCurlyBraces()
            => Assert.Equal(
                @"() => Interpolation($""abc {{!}}"")",
                ExpressionToCode.ToCode(() => Interpolation($"abc {{!}}")));

        [Fact]
        public void InterpolationWithOneArg()
            => Assert.Equal(
                @"() => $""abc {3.0f}""",
                ExpressionToCode.ToCode(() => $"abc {3f}"));

        [Fact]
        public void InterpolationWithTwoArgs()
            => Assert.Equal(
                @"() => $""abc {42} def {""ghi""}""",
                ExpressionToCode.ToCode(() => $"abc {42} def {"ghi"}"));

        [Fact]
        public void InterpolationWithThreeArgs()
        {
            var jkl = 123;
            Assert.Equal(
                @"() => $""abc {37} def {null} ghi {jkl} mno""",
                ExpressionToCode.ToCode(() => $"abc {37} def {null} ghi {jkl} mno"));
        }

        [Fact]
        public void InterpolationWithFourArgs()
        {
            var jkl = 123;
            var p = 2;
            var q = 3;
            var r = 4;
            Assert.Equal(
                @"() => $""abc {37} def {null} ghi {jkl} mno {p + q + r} stu""",
                ExpressionToCode.ToCode(() => $"abc {37} def {null} ghi {jkl} mno {p + q + r} stu"));
        }
    }
}


<|start_filename|>ExpressionToCodeLib/Internal/ObjectToCodeImpl.cs<|end_filename|>
﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace ExpressionToCodeLib.Internal
{
    static class ObjectToCodeImpl
    {
        static readonly string[] lineSeparators = { "\r\n", "\n" };

        public static string ComplexObjectToPseudoCode(ExpressionToCodeConfiguration config, object? val, int indent)
            => ComplexObjectToPseudoCode(config, val, indent, config.Value.MaximumValueLength ?? int.MaxValue);

        static string ComplexObjectToPseudoCode(ExpressionToCodeConfiguration config, object? val, int indent, int valueSize)
        {
            var retval = ObjectToCode.PlainObjectToCode(val);
            if (val is string) {
                return ElidePossiblyMultilineString(config, retval ?? throw new InvalidOperationException("retval cannot be null for strings"), indent, valueSize).Trim();
            } else if (retval != null) {
                return ElideAfter(retval, valueSize);
            } else if (val == null) {
                throw new Exception("Impossible: if val is null, retval cannot be");
            } else if (val is Array arrayVal) {
                return "new[] " + FormatEnumerable(config, arrayVal, indent, valueSize - 6);
            } else if (val is IEnumerable enumerableVal) {
                return FormatTypeWithListInitializerOrNull(config, val, indent, valueSize, enumerableVal) ?? FormatEnumerable(config, enumerableVal, indent, valueSize);
            } else if (val is Expression exprVal) {
                return ElideAfter(config.GetExpressionToCode().ToCode(exprVal), valueSize);
            } else if (val is IStructuralComparable tuple && val is IComparable && CSharpFriendlyTypeName.IsValueTupleType(val.GetType().GetTypeInfo())) {
                var collector = new NastyHackyTupleCollector();
                tuple.CompareTo(tuple, collector);
                var sb = new StringBuilder();
                sb.Append("(");
                for (var index = 0; index < collector.CollectedObjects.Count; index++) {
                    var item = collector.CollectedObjects[index];
                    var asString = ComplexObjectToPseudoCode(config, item, indent + 4, valueSize);
                    if (index > 0) {
                        sb.Append(", ");
                    }

                    sb.Append(asString);
                }

                sb.Append(")");
                return ElidePossiblyMultilineString(config, sb.ToString(), indent, valueSize).Trim();
            } else if (val.GetType().GuessTypeClass() == ReflectionHelpers.TypeClass.AnonymousType) {
                var type = val.GetType();
                return "new {" +
                    string.Join(
                        "",
                        type.GetTypeInfo()
                            .GetProperties()
                            .Select(
                                pi =>
                                    "\n" + new string(' ', indent + 4) + pi.Name + " = "
                                    + ComplexObjectToPseudoCode(config, pi.GetValue(val, null), indent + 4, valueSize - pi.Name.Length) + ",")
                    )
                    + "\n" + new string(' ', indent) + "}";
            } else {
                return ElideAfter(val.ToString(), valueSize);
            }
        }

        static string? FormatTypeWithListInitializerOrNull(ExpressionToCodeConfiguration config, object val, int indent, int valueSize, IEnumerable enumerableVal)
        {
            var type = val.GetType();
            if (!(type.GetConstructor(Type.EmptyTypes) is ConstructorInfo ci) || !ci.IsPublic) {
                return null;
            }
            foreach (var pi in type.GetProperties()) {
                if (
                    pi.Name == "Item"
                    && pi.CanWrite
                    && pi.GetIndexParameters() is ParameterInfo[] indexPars
                    && indexPars.Length == 1
                    && typeof(IEnumerable<>).MakeGenericType(typeof(KeyValuePair<,>).MakeGenericType(indexPars[0].ParameterType, pi.PropertyType)) is Type keyEnumerableType
                    && keyEnumerableType.IsAssignableFrom(type)
                ) {
                    var typeName = type.ToCSharpFriendlyTypeName();
                    return "new " + typeName + " " + PrintInitializerContents(config, enumerableVal, indexPars[0].ParameterType, pi.PropertyType, indent, valueSize - typeName.Length);
                }
            }
            return null;
        }

        sealed class NastyHackyTupleCollector : IComparer
        {
            //hack assumptions:
            // - A structural ordering comparer must in some way iterate over its contents.
            // - a tuple is defined to consider the order of its earlier members over that of its later members
            // - if earlier members are equal in order (compare==0), then it must call later members
            // - if everything is equal, it must call compareto on everything
            // - it would be inefficient to call compareto unnecessarily, so any tuple implementation is *likely* to call compareto in tuple-member-order, so it can exit early on non-zero comparison.
            public readonly List<object> CollectedObjects = new List<object>();
            int nesting = 1;

            public int Compare(object x, object y)
            {
                if (CollectedObjects.Count == nesting * 7 && x is IStructuralComparable tuple && tuple is IComparable && CSharpFriendlyTypeName.IsValueTupleType(tuple.GetType().GetTypeInfo())) {
                    nesting++;
                    return tuple.CompareTo(tuple, this);
                }

                CollectedObjects.Add(x);
                return 0;
            }
        }

        static string ElideAfter(string val, int len)
        {
            var maxLength = Math.Max(10, len);
            return val.Length > maxLength ? val.Substring(0, maxLength) + " ..." : val;
        }

        static string ElidePossiblyMultilineString(ExpressionToCodeConfiguration config, string val, int indent, int len)
        {
            var lines = val.Split(lineSeparators, StringSplitOptions.None);
            var indentString = new string(' ', indent);
            if (lines.Length < 2) {
                return "\n" + indentString + ElideAfter(val, len);
            }

            if (config.Value.PrintedListLengthLimit is int limit && lines.Length > limit) {
                lines = lines.Take(limit).Concat(new[] { "..." }).ToArray();
            }

            var stringBoundaryPrefix = lines[0].StartsWith("@\"", StringComparison.Ordinal) ? 2 : lines[0].StartsWith("\"", StringComparison.Ordinal) ? 1 : 0;
            var firstLineIndent = "\n" + indentString.Substring(0, Math.Max(0, indentString.Length - stringBoundaryPrefix));

            return firstLineIndent + string.Join("\n" + indentString, lines.Select(s => ElideAfter(s, len - 1)));
        }

        static string FormatEnumerable(ExpressionToCodeConfiguration config, IEnumerable list, int indent, int valueSize)
            => FormatLiteralInitializerContents(config, PrintListContents(config, list), indent, valueSize);

        static string FormatLiteralInitializerContents(ExpressionToCodeConfiguration config, IEnumerable<string> contents, int indent, int valueSize)
        {
            if (contents.Sum(s => s.Length + 2) > Math.Min(valueSize, 120) || contents.Any(s => s.Any(c => c == '\n'))) {
                return "{"
                    + string.Join("", contents.Select(s => ElidePossiblyMultilineString(config, s, indent + 4, valueSize - 3) + (s == "..." ? "" : ",")))
                    + "\n" + new string(' ', indent)
                    + "}";
            }

            return "{ " + string.Join(", ", contents) + " }";
        }

        static IEnumerable<string> PrintListContents(ExpressionToCodeConfiguration config, IEnumerable list)
        {
            var count = 0;
            foreach (var item in list) {
                count++;
                if (count > config.Value.PrintedListLengthLimit) {
                    yield return "...";
                    yield break;
                } else {
                    yield return ComplexObjectToPseudoCode(config, item, 0);
                }
            }
        }

        static readonly ConcurrentDictionary<(Type, Type), IInitializerStringifier> initializerStringifiers = new ConcurrentDictionary<(Type, Type), IInitializerStringifier>();

        public static string PrintInitializerContents(ExpressionToCodeConfiguration config, IEnumerable list, Type keyType, Type valueType, int indent, int valueSize)
        {
            if (!initializerStringifiers.TryGetValue((keyType, valueType), out var stringify)) {
                stringify = (IInitializerStringifier)Activator.CreateInstance(typeof(InitializerStringifier<,>).MakeGenericType(keyType, valueType));
                initializerStringifiers[(keyType, valueType)] = stringify;
            }
            return FormatLiteralInitializerContents(config, stringify.PrintInitializerContents(config, list).ToArray(), indent, valueSize);
        }

        interface IInitializerStringifier
        {
            IEnumerable<string> PrintInitializerContents(ExpressionToCodeConfiguration config, IEnumerable list);
        }

        sealed class InitializerStringifier<TKey, TValue> : IInitializerStringifier
        {
            static IEnumerable<string> PrintInitializerContents(ExpressionToCodeConfiguration config, IEnumerable<KeyValuePair<TKey, TValue>> list)
            {
                var count = 0;
                foreach (var item in list) {
                    count++;
                    if (count > config.Value.PrintedListLengthLimit) {
                        yield return "...";
                        yield break;
                    } else {
                        yield return $"[{ComplexObjectToPseudoCode(config, item.Key, 0)}] = {ComplexObjectToPseudoCode(config, item.Value, 0)}";
                    }
                }
            }

            public IEnumerable<string> PrintInitializerContents(ExpressionToCodeConfiguration config, IEnumerable list)
                => PrintInitializerContents(config, (IEnumerable<KeyValuePair<TKey, TValue>>)list);
        }

        public static string? ExpressionValueAsCode(ExpressionToCodeConfiguration config, Expression expression, int indent)
        {
            try {
                Delegate lambda;
                try {
                    lambda = config.Value.ExpressionCompiler.Compile(Expression.Lambda(expression));
                } catch (InvalidOperationException) {
                    return null;
                }

                var val = lambda.DynamicInvoke();
                try {
                    return ComplexObjectToPseudoCode(config, val, indent);
                } catch (Exception e) {
                    return "stringification throws " + e.GetType().FullName;
                }
            } catch (TargetInvocationException tie) {
                return "throws " + tie.InnerException?.GetType().FullName;
            }
        }
    }
}


<|start_filename|>ExpressionToCodeLib/ExpressionToCode.cs<|end_filename|>
﻿using System;
using System.Linq.Expressions;

// ReSharper disable UnusedMember.Global
// ReSharper disable MemberCanBePrivate.Global
namespace ExpressionToCodeLib
{
    /// <summary>
    ///     If you wish to override some formatting aspects of these methods, set
    ///     ExpressionToCodeConfiguration.GlobalCodeGetConfiguration.
    /// </summary>
    public static class ExpressionToCode
    {
        public static string ToCode<T, T1, T2, T3>(Expression<Func<T, T1, T2, T3>> e)
            => ToCode((Expression)e);

        public static string ToCode<T, T1, T2>(Expression<Func<T, T1, T2>> e)
            => ToCode((Expression)e);

        public static string ToCode<T, T1>(Expression<Func<T, T1>> e)
            => ToCode((Expression)e);

        public static string ToCode<T>(Expression<Func<T>> e)
            => ToCode((Expression)e);

        public static string ToCode(Expression e)
            => ExpressionToCodeConfiguration.GlobalCodeGenConfiguration.GetExpressionToCode().ToCode(e);

        public static string AnnotatedToCode<T, T1, T2, T3>(Expression<Func<T, T1, T2, T3>> e)
            => AnnotatedToCode((Expression)e);

        public static string AnnotatedToCode<T, T1, T2>(Expression<Func<T, T1, T2>> e)
            => AnnotatedToCode((Expression)e);

        public static string AnnotatedToCode<T, T1>(Expression<Func<T, T1>> e)
            => AnnotatedToCode((Expression)e);

        public static string AnnotatedToCode<T>(Expression<Func<T>> e)
            => AnnotatedToCode((Expression)e);

        public static string AnnotatedToCode(Expression expr)
            => AnnotatedToCode(ExpressionToCodeConfiguration.GlobalCodeGenConfiguration, expr);

        public static string ToCode<T, T1, T2, T3>(this ExpressionToCodeConfiguration config, Expression<Func<T, T1, T2, T3>> e)
            => config.GetExpressionToCode().ToCode(e);

        public static string ToCode<T, T1, T2>(this ExpressionToCodeConfiguration config, Expression<Func<T, T1, T2>> e)
            => config.GetExpressionToCode().ToCode(e);

        public static string ToCode<T, T1>(this ExpressionToCodeConfiguration config, Expression<Func<T, T1>> e)
            => config.GetExpressionToCode().ToCode(e);

        public static string ToCode<T>(this ExpressionToCodeConfiguration config, Expression<Func<T>> e)
            => config.GetExpressionToCode().ToCode(e);

        public static string ToCode(this ExpressionToCodeConfiguration config, Expression e)
            => config.GetExpressionToCode().ToCode(e);

        public static string AnnotatedToCode<T, T1, T2, T3>(this ExpressionToCodeConfiguration config, Expression<Func<T, T1, T2, T3>> e)
            => AnnotatedToCode(config, (Expression)e);

        public static string AnnotatedToCode<T, T1, T2>(this ExpressionToCodeConfiguration config, Expression<Func<T, T1, T2>> e)
            => AnnotatedToCode(config, (Expression)e);

        public static string AnnotatedToCode<T, T1>(this ExpressionToCodeConfiguration config, Expression<Func<T, T1>> e)
            => AnnotatedToCode(config, (Expression)e);

        public static string AnnotatedToCode<T>(this ExpressionToCodeConfiguration config, Expression<Func<T>> e)
            => AnnotatedToCode(config, (Expression)e);

        internal static bool ShouldIgnoreSpaceAfter(char c)
            => c == ' ' || c == '(';

        public static string AnnotatedToCode(this ExpressionToCodeConfiguration config, Expression expr)
            => config.Value.CodeAnnotator.AnnotateExpressionTree(config, expr, null, false);

        /// <summary>
        ///     Converts expression to variable/property/method C# like representation adding it's string value.
        /// </summary>
        /// <example>
        ///     string toNameValueRepresentation = "Value";
        ///     ToRepr(() => toNameValueRepresentation); // "toNameValueRepresentation = Value"
        /// </example>
        /// <remarks>
        ///     Unlike <see cref="ExpressionToCode.ToCode(Expression)" />(which targets compilable output), this method is geared
        ///     towards dumping simple objects into text, so may skip some C# issues for sake of readability.
        /// </remarks>
        public static string ToValuedCode<TResult>(this Expression<Func<TResult>> expression)
        {
            TResult retValue;
            try {
                retValue = ExpressionToCodeConfiguration.GlobalAssertionConfiguration.Value.ExpressionCompiler.Compile(expression)();
            } catch (Exception ex) {
                throw new InvalidOperationException("Cannon get return value of expression when it throws error", ex);
            }

            return ToCode(expression.Body) + " = " + retValue;
        }

        /// <summary>
        ///     Gets property, variable or method name from lambda expression.
        /// </summary>
        /// <param name="expression">Simple expression to obtain name from.</param>
        /// <returns>The `name` of expression.</returns>
        /// <example>
        ///     var example = "some text";
        ///     var name = ExpressionToCode.GetNameIn(() => example);  // "example"
        /// </example>
        /// <exception cref="System.ArgumentException">Unsupported or unknown or complex expression to get `name` of it.</exception>
        public static string GetNameIn<TResult>(Expression<Func<TResult>> expression)
            => GetNameIn((Expression)expression);

        public static string GetNameIn(Expression<Action> expression)
            => GetNameIn((Expression)expression);

        public static string GetNameIn(Expression expr)
        {
            if (expr is MethodCallExpression callExpr) {
                return callExpr.Method.Name;
            }

            if (expr is MemberExpression accessExpr) {
                return accessExpr.Member.Name;
            }

            if (expr.NodeType == ExpressionType.ArrayLength) {
                return "Length";
            }

            if (expr is LambdaExpression lambdaExpr) {
                return GetNameIn(lambdaExpr.Body);
            }

            if (expr is UnaryExpression unary) {
                return GetNameIn(unary.Operand);
            }

            throw new ArgumentException("Unsupported or unknown or complex expression to get `name` of it", nameof(expr));
        }
    }

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

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

        public static string ToCode<T, T1, T2>(this IExpressionToCode it, Expression<Func<T, T1, T2>> e)
            => it.ToCode(e);

        public static string ToCode<T, T1>(this IExpressionToCode it, Expression<Func<T, T1>> e)
            => it.ToCode(e);

        public static string ToCode<T>(this IExpressionToCode it, Expression<Func<T>> e)
            => it.ToCode(e);
    }

    public interface IAnnotatedToCode
    {
        string AnnotatedToCode(Expression e, string? msg, bool outerValueIsAssertionFailure);
    }

    public static class AnnotatedToCodeExtensions
    {
        public static string AnnotatedToCode(this IAnnotatedToCode it, Expression e)
            => it.AnnotatedToCode(e, null, false);

        public static string AnnotatedToCode<T, T1, T2, T3>(this IAnnotatedToCode it, Expression<Func<T, T1, T2, T3>> e)
            => it.AnnotatedToCode(e, null, false);

        public static string AnnotatedToCode<T, T1, T2>(this IAnnotatedToCode it, Expression<Func<T, T1, T2>> e)
            => it.AnnotatedToCode(e, null, false);

        public static string AnnotatedToCode<T, T1>(this IAnnotatedToCode it, Expression<Func<T, T1>> e)
            => it.AnnotatedToCode(e, null, false);

        public static string AnnotatedToCode<T>(this IAnnotatedToCode it, Expression<Func<T>> e)
            => it.AnnotatedToCode(e, null, false);

        public static string AnnotatedToCode<T, T1, T2, T3>(this IAnnotatedToCode it, Expression<Func<T, T1, T2, T3>> e, string? msg, bool outerValueIsAssertionFailure)
            // ReSharper disable once RedundantCast
            => it.AnnotatedToCode((Expression)e, msg, outerValueIsAssertionFailure);

        public static string AnnotatedToCode<T, T1, T2>(this IAnnotatedToCode it, Expression<Func<T, T1, T2>> e, string? msg, bool outerValueIsAssertionFailure)
            // ReSharper disable once RedundantCast
            => it.AnnotatedToCode((Expression)e, msg, outerValueIsAssertionFailure);

        public static string AnnotatedToCode<T, T1>(this IAnnotatedToCode it, Expression<Func<T, T1>> e, string? msg, bool outerValueIsAssertionFailure)
            // ReSharper disable once RedundantCast
            => it.AnnotatedToCode((Expression)e, msg, outerValueIsAssertionFailure);

        public static string AnnotatedToCode<T>(this IAnnotatedToCode it, Expression<Func<T>> e, string? msg, bool outerValueIsAssertionFailure)
            // ReSharper disable once RedundantCast
            => it.AnnotatedToCode((Expression)e, msg, outerValueIsAssertionFailure);
    }
}


<|start_filename|>ExpressionToCodeLib/Internal/CSharpFriendlyTypeName.cs<|end_filename|>
﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ExpressionToCodeLib.Internal
{
    struct CSharpFriendlyTypeName
    {
        public bool UseFullName;
        public bool IncludeGenericTypeArgumentNames;

        public string GetTypeName(Type type)
            => AliasNameOrNull(type) ?? NullableTypeNameOrNull(type.GetTypeInfo()) ?? ArrayTypeNameOrNull(type) ?? ValueTupleTypeNameOrNull(type) ?? GetUnaliasedTypeName(type);

        string? ValueTupleTypeNameOrNull(Type type)
        {
            if (!IsValueTupleType(type.GetTypeInfo())) {
                return null;
            }

            var output = new StringBuilder();
            output.Append("(");
            var genericArguments = type.GetTypeInfo().GetGenericArguments();
            var nextIdx = 0;
            while (nextIdx < genericArguments.Length) {
                var typePar = genericArguments[nextIdx];
                if (nextIdx + 1 == genericArguments.Length) {
                    if (nextIdx == 7 && IsValueTupleType(typePar.GetTypeInfo())) {
                        genericArguments = typePar.GetTypeInfo().GetGenericArguments();
                        nextIdx = 0;
                    } else {
                        output.Append(GetTypeName(typePar));
                        break;
                    }
                } else {
                    output.Append(GetTypeName(typePar));
                    output.Append(", ");
                    nextIdx++;
                }
            }

            output.Append(")");
            return output.ToString();
        }

        public static bool IsValueTupleType(TypeInfo typeInfo)
            => typeInfo.IsGenericType && !typeInfo.IsGenericTypeDefinition && typeInfo.Namespace == "System" && typeInfo.Name.StartsWith("ValueTuple`", StringComparison.Ordinal);

        string GetUnaliasedTypeName(Type type)
        {
            var typeNameWithoutNamespace =
                GenericTypeName(type)
                ?? NormalName(type);
            return UseFullName ? type.Namespace + "." + typeNameWithoutNamespace : typeNameWithoutNamespace;
        }

        static string? AliasNameOrNull(Type type)
        {
            if (type == typeof(bool)) {
                return "bool";
            } else if (type == typeof(byte)) {
                return "byte";
            } else if (type == typeof(sbyte)) {
                return "sbyte";
            } else if (type == typeof(char)) {
                return "char";
            } else if (type == typeof(decimal)) {
                return "decimal";
            } else if (type == typeof(double)) {
                return "double";
            } else if (type == typeof(float)) {
                return "float";
            } else if (type == typeof(int)) {
                return "int";
            } else if (type == typeof(uint)) {
                return "uint";
            } else if (type == typeof(long)) {
                return "long";
            } else if (type == typeof(ulong)) {
                return "ulong";
            } else if (type == typeof(object)) {
                return "object";
            } else if (type == typeof(short)) {
                return "short";
            } else if (type == typeof(ushort)) {
                return "ushort";
            } else if (type == typeof(string)) {
                return "string";
            } else if (type == typeof(void)) {
                return "void";
            } else if (type.IsGenericParameter) {
                return type.Name;
            } else {
                return null;
            }
        }

        string? NullableTypeNameOrNull(TypeInfo typeInfo)
            => typeInfo.IsGenericType && !typeInfo.IsGenericTypeDefinition && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>) ? GetTypeName(typeInfo.GetGenericArguments().Single()) + "?" : null;

        string NormalName(Type type)
        {
            if (type.DeclaringType != null) {
                var settingsWithoutUseFullname = this;
                settingsWithoutUseFullname.UseFullName = false;

                return settingsWithoutUseFullname.GetTypeName(type.DeclaringType) + "." + type.Name;
            } else {
                return type.Name;
            }
        }

        string? GenericTypeName(Type type)
        {
            if (!type.GetTypeInfo().IsGenericType) {
                return null;
            }

            var renderAsGenericTypeDefinition = !IncludeGenericTypeArgumentNames && type.GetTypeInfo().IsGenericTypeDefinition;

            var typeArgs = type.GetTypeInfo().GetGenericArguments();
            var typeArgIdx = typeArgs.Length;
            var revNestedTypeNames = new List<string>();

            while (type != null) {
                var name = type.Name;
                var backtickIdx = name.IndexOf('`');
                if (backtickIdx == -1) {
                    revNestedTypeNames.Add(name);
                } else {
                    var afterArgCountIdx = name.IndexOf('[', backtickIdx + 1);
                    if (afterArgCountIdx == -1) {
                        afterArgCountIdx = name.Length;
                    }

                    var thisTypeArgCount = int.Parse(name.Substring(backtickIdx + 1, afterArgCountIdx - backtickIdx - 1));
                    if (renderAsGenericTypeDefinition) {
                        typeArgIdx -= thisTypeArgCount;
                        revNestedTypeNames.Add(name.Substring(0, backtickIdx) + "<" + new string(',', thisTypeArgCount - 1) + ">");
                    } else {
                        var argNames = new List<string>();
                        for (var i = typeArgIdx - thisTypeArgCount; i < typeArgIdx; i++) {
                            argNames.Add(GetTypeName(typeArgs[i]));
                        }

                        typeArgIdx -= thisTypeArgCount;
                        revNestedTypeNames.Add(name.Substring(0, backtickIdx) + "<" + string.Join(", ", argNames) + ">");
                    }
                }

                type = type.DeclaringType;
            }

            revNestedTypeNames.Reverse();
            return string.Join(".", revNestedTypeNames);
        }

        string? ArrayTypeNameOrNull(Type type)
        {
            if (!type.IsArray) {
                return null;
            }

            var arraySuffix = default(string?);
            do {
                var rankCommas = new string(',', type.GetArrayRank() - 1);
                type = type.GetElementType();
                arraySuffix = arraySuffix + "[" + rankCommas + "]";
                // ReSharper disable once PossibleNullReferenceException
            } while (type.IsArray);

            var basename = GetTypeName(type);
            return basename + arraySuffix;
        }
    }
}


<|start_filename|>ExpressionToCodeLib/Internal/ObjectStringifyImpl.cs<|end_filename|>
﻿using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;

namespace ExpressionToCodeLib.Internal
{
    sealed class ObjectStringifyImpl : IObjectStringifier
    {
        readonly bool fullTypeNames;

        public ObjectStringifyImpl(bool fullTypeNames = false)
            => this.fullTypeNames = fullTypeNames;

        public string TypeNameToCode(Type type)
            => new CSharpFriendlyTypeName { UseFullName = fullTypeNames }.GetTypeName(type);

        public string? PlainObjectToCode(object? val, Type? type)
            => val switch {
                null when type == null || type == typeof(object) => "null",
                null => "default(" + TypeNameToCode(type) + ")",
                string str => PreferLiteralSyntax(str) ? "@\"" + str.Replace("\"", "\"\"") + "\"" : "\"" + EscapeStringChars(str) + "\"",
                char charVal => "'" + EscapeCharForString(charVal) + "'",
                decimal _ => Convert.ToString(val, CultureInfo.InvariantCulture) + "m",
                float floatVal => FloatToCode(floatVal),
                double doubleVal => DoubleToCode(doubleVal),
                byte byteVal => "((byte)" + byteVal + ")",
                sbyte sbyteVal => "((sbyte)" + sbyteVal + ")",
                short shortVal => "((short)" + shortVal + ")",
                ushort ushortVal => "((ushort)" + ushortVal + ")",
                int intVal => intVal.ToString(),
                uint uintVal => uintVal + "U",
                long longVal => longVal + "L",
                ulong ulongVal => ulongVal + "UL",
                bool boolVal => boolVal ? "true" : "false",
                Enum enumVal => EnumValueToCode(val, enumVal),
                Type typeVal => "typeof(" + TypeNameToCode(typeVal) + ")",
                MethodInfo methodInfoVal => TypeNameToCode(methodInfoVal.DeclaringType) + "." + methodInfoVal.Name,
                _ when val is ValueType && Activator.CreateInstance(val.GetType()).Equals(val) => "default(" + TypeNameToCode(val.GetType()) + ")",
                _ => null
            };

        string EnumValueToCode(object val, Enum enumVal)
        {
            if (Enum.IsDefined(enumVal.GetType(), enumVal)) {
                return TypeNameToCode(enumVal.GetType()) + "." + enumVal;
            } else {
                var enumAsLong = ((IConvertible)enumVal).ToInt64(null);
                var toString = enumVal.ToString();
                if (toString == enumAsLong.ToString()) {
                    return "((" + TypeNameToCode(enumVal.GetType()) + ")" + enumAsLong + ")";
                } else {
                    var components = toString.Split(new[] { ", " }, StringSplitOptions.RemoveEmptyEntries);
                    return components.Length == 0
                        ? "default(" + TypeNameToCode(enumVal.GetType()) + ")"
                        : components.Length == 1
                            ? TypeNameToCode(enumVal.GetType()) + "." + components[0]
                            : "(" + string.Join(" | ", components.Select(s => TypeNameToCode(val.GetType()) + "." + s)) + ")";
                }
            }
        }

        internal static bool PreferLiteralSyntax(string str1)
        {
            var count = 0;
            foreach (var c in str1) {
                if (c < 32 && c != '\r' || c == '\\') {
                    count++;
                    if (count > 3) {
                        return true;
                    }
                }
            }

            return false;
        }

        static string EscapeCharForString(char c)
        {
            if (c < 32 || CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.Control) {
                //this is a little too rigorous; but easier to read
                if (c == '\r') {
                    return "\\r";
                } else if (c == '\t') {
                    return "\\t";
                } else if (c == '\n') {
                    return "\\n";
                } else {
                    return "\\x" + Convert.ToString(c, 16);
                }
            } else if (c == '\\') {
                return @"\\";
            } else if (c == '\"') {
                return "\\\"";
            } else {
                return c.ToString();
            }
        }

        internal static string EscapeStringChars(string str)
        {
            var sb = new StringBuilder(str.Length);
            foreach (var c in str) {
                sb.Append(EscapeCharForString(c));
            }

            return sb.ToString();
        }

        static string DoubleToCode(double p)
        {
            if (double.IsNaN(p)) {
                return "double.NaN";
            } else if (double.IsNegativeInfinity(p)) {
                return "double.NegativeInfinity";
            } else if (double.IsPositiveInfinity(p)) {
                return "double.PositiveInfinity";
            } else if (Math.Abs(p) > uint.MaxValue) {
                return p.ToString("0.0########################e0", CultureInfo.InvariantCulture);
            } else {
                return p.ToString("0.0########################", CultureInfo.InvariantCulture);
            }
        }

        static string FloatToCode(float p)
        {
            if (float.IsNaN(p)) {
                return "float.NaN";
            } else if (float.IsNegativeInfinity(p)) {
                return "float.NegativeInfinity";
            } else if (float.IsPositiveInfinity(p)) {
                return "float.PositiveInfinity";
            } else if (Math.Abs(p) >= 1 << 24) {
                return p.ToString("0.0########e0", CultureInfo.InvariantCulture) + "f";
            } else {
                return p.ToString("0.0########", CultureInfo.InvariantCulture) + "f";
            }
        }
    }
}


<|start_filename|>ExpressionToCodeLib/Internal/FastExpressionCompilerImpl.cs<|end_filename|>
﻿using System;
using System.Linq.Expressions;
using FastExpressionCompiler;

namespace ExpressionToCodeLib.Internal
{
    sealed class FastExpressionCompilerImpl : IExpressionCompiler
    {
        static readonly DotnetExpressionCompiler fallback = new DotnetExpressionCompiler();

        public Func<T> Compile<T>(Expression<Func<T>> expression)
            => expression.TryCompile<Func<T>>() ?? fallback.Compile(expression);

        public Delegate Compile(LambdaExpression expression)
            => expression.TryCompile<Delegate>() ?? fallback.Compile(expression);
    }
}


<|start_filename|>ExpressionToCodeTest/MultipleIndexerTest.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using ExpressionToCodeLib;
using Xunit;

namespace ExpressionToCodeTest
{
    sealed class HasIndexers
    {
        public object? this[string s] => null;
        public object? this[int i] => null;
    }

    public sealed class MultipleIndexerTest
    {
        [Fact]
        public void CanPrettyPrintVariousIndexers()
            => Assert.Equal(
                "() => new HasIndexers()[3] == new HasIndexers()[\"three\"]",
                ExpressionToCode.ToCode(() => new HasIndexers()[3] == new HasIndexers()["three"])
            );
    }
}


<|start_filename|>TopLevelProgramExample/TopLevelProgramMarker.cs<|end_filename|>
﻿namespace TopLevelProgramExample
{
    public static class TopLevelProgramMarker
    {
        public static string LambdaToMyVar;
        public static string LambdaInsideLocalFunction;
        public static string LambdaInsideNestedClassMethod;
    }
}


<|start_filename|>ExpressionToCodeTest/ToValuedCodeTest.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using ExpressionToCodeLib;
using Xunit;

namespace ExpressionToCodeTest
{
    public sealed class ToValuedCodeTest
    {
        [Fact]
        public void ToValuedCode_ofNull_fails()
        {
            ExpressionToCodeLibTest? obj = null;

            Assert.ThrowsAny<InvalidOperationException>(
#pragma warning disable CS8602 // Dereference of a possibly null reference.
                () => ExpressionToCode.ToValuedCode(() => obj.TheProperty)
#pragma warning restore CS8602 // Dereference of a possibly null reference.
            );
        }

        [Fact]
        public void ArrayLength()
        {
            var arr = new[] { 1 };
            var actual = ExpressionToCode.ToValuedCode(() => arr.Length);
            Assert.Equal("arr.Length = 1", actual);
        }

        [Fact]
        public void ThePropertyAccess()
        {
            var actual = ExpressionToCode.ToValuedCode(() => TheProperty);
            Assert.Equal("ToValuedCodeTest.TheProperty = TheValue", actual);
        }

        [Fact]
        public void TheStringVariable()
        {
            var theVariable = "theValue";
            var actual = ExpressionToCode.ToValuedCode(() => theVariable);
            Assert.Equal("theVariable = theValue", actual);
        }

        [Fact]
        public void MethodCall()
        {
            var actual = ExpressionToCode.ToValuedCode(() => TheMethod(1, "2"));
            Assert.Equal("TheMethod(1, \"2\") = TheMethod 1 2", actual);
        }

        [Fact]
        public void GenericMethodCall()
        {
            var actual = ExpressionToCode.ToValuedCode(() => TheGenericMethod<int>(2));
            Assert.Equal("TheGenericMethod<int>(2) = Return value is 4", actual);
        }

        [Fact]
        public void ThisIndexedProperty()
        {
            var actual = ExpressionToCode.ToValuedCode(() => this[1]);
            Assert.Equal("this[1] = TheIndexedValue", actual);
        }

        [Fact]
        public void ThisMethodCall()
        {
            var code = ExpressionToCode.ToValuedCode(() => ReturnZero());

            Assert.Equal("ReturnZero() = 0", code);
        }

        [Fact]
        public void ThisStaticMethodCall()
        {
            var code = ExpressionToCode.ToValuedCode(() => StaticReturnZero());

            Assert.Equal("ToValuedCodeTest.StaticReturnZero() = 0", code);
        }

        static string TheProperty
            => "TheValue";

        // ReSharper disable once UnusedParameter.Local
        string this[int index]
            => "TheIndexedValue";

        static int StaticReturnZero()
            => 0;

        // ReSharper disable MemberCanBeMadeStatic.Local
        string TheMethod(int parameter1, string parameter2)
            => "TheMethod " + parameter1 + " " + parameter2;

        // ReSharper disable once UnusedTypeParameter
        string TheGenericMethod<T>(int two)
            => "Return value is " + two * two;

        int ReturnZero()
            => 0;
    }
}


<|start_filename|>ExpressionToCodeLib/Internal/ExpressionTypeDispatch.Generated.cs<|end_filename|>
﻿using System;
using System.Linq.Expressions;
using System.Diagnostics.Contracts;

namespace ExpressionToCodeLib.Internal {
  internal interface IExpressionTypeDispatch<T> {
		[Pure] T DispatchAdd(Expression e);
		[Pure] T DispatchAddChecked(Expression e);
		[Pure] T DispatchAnd(Expression e);
		[Pure] T DispatchAndAlso(Expression e);
		[Pure] T DispatchArrayLength(Expression e);
		[Pure] T DispatchArrayIndex(Expression e);
		[Pure] T DispatchCall(Expression e);
		[Pure] T DispatchCoalesce(Expression e);
		[Pure] T DispatchConditional(Expression e);
		[Pure] T DispatchConstant(Expression e);
		[Pure] T DispatchConvert(Expression e);
		[Pure] T DispatchConvertChecked(Expression e);
		[Pure] T DispatchDivide(Expression e);
		[Pure] T DispatchEqual(Expression e);
		[Pure] T DispatchExclusiveOr(Expression e);
		[Pure] T DispatchGreaterThan(Expression e);
		[Pure] T DispatchGreaterThanOrEqual(Expression e);
		[Pure] T DispatchInvoke(Expression e);
		[Pure] T DispatchLambda(Expression e);
		[Pure] T DispatchLeftShift(Expression e);
		[Pure] T DispatchLessThan(Expression e);
		[Pure] T DispatchLessThanOrEqual(Expression e);
		[Pure] T DispatchListInit(Expression e);
		[Pure] T DispatchMemberAccess(Expression e);
		[Pure] T DispatchMemberInit(Expression e);
		[Pure] T DispatchModulo(Expression e);
		[Pure] T DispatchMultiply(Expression e);
		[Pure] T DispatchMultiplyChecked(Expression e);
		[Pure] T DispatchNegate(Expression e);
		[Pure] T DispatchUnaryPlus(Expression e);
		[Pure] T DispatchNegateChecked(Expression e);
		[Pure] T DispatchNew(Expression e);
		[Pure] T DispatchNewArrayInit(Expression e);
		[Pure] T DispatchNewArrayBounds(Expression e);
		[Pure] T DispatchNot(Expression e);
		[Pure] T DispatchNotEqual(Expression e);
		[Pure] T DispatchOr(Expression e);
		[Pure] T DispatchOrElse(Expression e);
		[Pure] T DispatchParameter(Expression e);
		[Pure] T DispatchPower(Expression e);
		[Pure] T DispatchQuote(Expression e);
		[Pure] T DispatchRightShift(Expression e);
		[Pure] T DispatchSubtract(Expression e);
		[Pure] T DispatchSubtractChecked(Expression e);
		[Pure] T DispatchTypeAs(Expression e);
		[Pure] T DispatchTypeIs(Expression e);
		[Pure] T DispatchAssign(Expression e);
		[Pure] T DispatchBlock(Expression e);
		[Pure] T DispatchDebugInfo(Expression e);
		[Pure] T DispatchDecrement(Expression e);
		[Pure] T DispatchDynamic(Expression e);
		[Pure] T DispatchDefault(Expression e);
		[Pure] T DispatchExtension(Expression e);
		[Pure] T DispatchGoto(Expression e);
		[Pure] T DispatchIncrement(Expression e);
		[Pure] T DispatchIndex(Expression e);
		[Pure] T DispatchLabel(Expression e);
		[Pure] T DispatchRuntimeVariables(Expression e);
		[Pure] T DispatchLoop(Expression e);
		[Pure] T DispatchSwitch(Expression e);
		[Pure] T DispatchThrow(Expression e);
		[Pure] T DispatchTry(Expression e);
		[Pure] T DispatchUnbox(Expression e);
		[Pure] T DispatchAddAssign(Expression e);
		[Pure] T DispatchAndAssign(Expression e);
		[Pure] T DispatchDivideAssign(Expression e);
		[Pure] T DispatchExclusiveOrAssign(Expression e);
		[Pure] T DispatchLeftShiftAssign(Expression e);
		[Pure] T DispatchModuloAssign(Expression e);
		[Pure] T DispatchMultiplyAssign(Expression e);
		[Pure] T DispatchOrAssign(Expression e);
		[Pure] T DispatchPowerAssign(Expression e);
		[Pure] T DispatchRightShiftAssign(Expression e);
		[Pure] T DispatchSubtractAssign(Expression e);
		[Pure] T DispatchAddAssignChecked(Expression e);
		[Pure] T DispatchMultiplyAssignChecked(Expression e);
		[Pure] T DispatchSubtractAssignChecked(Expression e);
		[Pure] T DispatchPreIncrementAssign(Expression e);
		[Pure] T DispatchPreDecrementAssign(Expression e);
		[Pure] T DispatchPostIncrementAssign(Expression e);
		[Pure] T DispatchPostDecrementAssign(Expression e);
		[Pure] T DispatchTypeEqual(Expression e);
		[Pure] T DispatchOnesComplement(Expression e);
		[Pure] T DispatchIsTrue(Expression e);
		[Pure] T DispatchIsFalse(Expression e);
  }

    internal static class ExpressionTypeDispatcher {
		[Pure] public static T ExpressionDispatch<T>(this IExpressionTypeDispatch<T> dispatcher, Expression e) {
			try{
				switch(e.NodeType) {
					case ExpressionType.Add: return dispatcher.DispatchAdd(e);
					case ExpressionType.AddChecked: return dispatcher.DispatchAddChecked(e);
					case ExpressionType.And: return dispatcher.DispatchAnd(e);
					case ExpressionType.AndAlso: return dispatcher.DispatchAndAlso(e);
					case ExpressionType.ArrayLength: return dispatcher.DispatchArrayLength(e);
					case ExpressionType.ArrayIndex: return dispatcher.DispatchArrayIndex(e);
					case ExpressionType.Call: return dispatcher.DispatchCall(e);
					case ExpressionType.Coalesce: return dispatcher.DispatchCoalesce(e);
					case ExpressionType.Conditional: return dispatcher.DispatchConditional(e);
					case ExpressionType.Constant: return dispatcher.DispatchConstant(e);
					case ExpressionType.Convert: return dispatcher.DispatchConvert(e);
					case ExpressionType.ConvertChecked: return dispatcher.DispatchConvertChecked(e);
					case ExpressionType.Divide: return dispatcher.DispatchDivide(e);
					case ExpressionType.Equal: return dispatcher.DispatchEqual(e);
					case ExpressionType.ExclusiveOr: return dispatcher.DispatchExclusiveOr(e);
					case ExpressionType.GreaterThan: return dispatcher.DispatchGreaterThan(e);
					case ExpressionType.GreaterThanOrEqual: return dispatcher.DispatchGreaterThanOrEqual(e);
					case ExpressionType.Invoke: return dispatcher.DispatchInvoke(e);
					case ExpressionType.Lambda: return dispatcher.DispatchLambda(e);
					case ExpressionType.LeftShift: return dispatcher.DispatchLeftShift(e);
					case ExpressionType.LessThan: return dispatcher.DispatchLessThan(e);
					case ExpressionType.LessThanOrEqual: return dispatcher.DispatchLessThanOrEqual(e);
					case ExpressionType.ListInit: return dispatcher.DispatchListInit(e);
					case ExpressionType.MemberAccess: return dispatcher.DispatchMemberAccess(e);
					case ExpressionType.MemberInit: return dispatcher.DispatchMemberInit(e);
					case ExpressionType.Modulo: return dispatcher.DispatchModulo(e);
					case ExpressionType.Multiply: return dispatcher.DispatchMultiply(e);
					case ExpressionType.MultiplyChecked: return dispatcher.DispatchMultiplyChecked(e);
					case ExpressionType.Negate: return dispatcher.DispatchNegate(e);
					case ExpressionType.UnaryPlus: return dispatcher.DispatchUnaryPlus(e);
					case ExpressionType.NegateChecked: return dispatcher.DispatchNegateChecked(e);
					case ExpressionType.New: return dispatcher.DispatchNew(e);
					case ExpressionType.NewArrayInit: return dispatcher.DispatchNewArrayInit(e);
					case ExpressionType.NewArrayBounds: return dispatcher.DispatchNewArrayBounds(e);
					case ExpressionType.Not: return dispatcher.DispatchNot(e);
					case ExpressionType.NotEqual: return dispatcher.DispatchNotEqual(e);
					case ExpressionType.Or: return dispatcher.DispatchOr(e);
					case ExpressionType.OrElse: return dispatcher.DispatchOrElse(e);
					case ExpressionType.Parameter: return dispatcher.DispatchParameter(e);
					case ExpressionType.Power: return dispatcher.DispatchPower(e);
					case ExpressionType.Quote: return dispatcher.DispatchQuote(e);
					case ExpressionType.RightShift: return dispatcher.DispatchRightShift(e);
					case ExpressionType.Subtract: return dispatcher.DispatchSubtract(e);
					case ExpressionType.SubtractChecked: return dispatcher.DispatchSubtractChecked(e);
					case ExpressionType.TypeAs: return dispatcher.DispatchTypeAs(e);
					case ExpressionType.TypeIs: return dispatcher.DispatchTypeIs(e);
					case ExpressionType.Assign: return dispatcher.DispatchAssign(e);
					case ExpressionType.Block: return dispatcher.DispatchBlock(e);
					case ExpressionType.DebugInfo: return dispatcher.DispatchDebugInfo(e);
					case ExpressionType.Decrement: return dispatcher.DispatchDecrement(e);
					case ExpressionType.Dynamic: return dispatcher.DispatchDynamic(e);
					case ExpressionType.Default: return dispatcher.DispatchDefault(e);
					case ExpressionType.Extension: return dispatcher.DispatchExtension(e);
					case ExpressionType.Goto: return dispatcher.DispatchGoto(e);
					case ExpressionType.Increment: return dispatcher.DispatchIncrement(e);
					case ExpressionType.Index: return dispatcher.DispatchIndex(e);
					case ExpressionType.Label: return dispatcher.DispatchLabel(e);
					case ExpressionType.RuntimeVariables: return dispatcher.DispatchRuntimeVariables(e);
					case ExpressionType.Loop: return dispatcher.DispatchLoop(e);
					case ExpressionType.Switch: return dispatcher.DispatchSwitch(e);
					case ExpressionType.Throw: return dispatcher.DispatchThrow(e);
					case ExpressionType.Try: return dispatcher.DispatchTry(e);
					case ExpressionType.Unbox: return dispatcher.DispatchUnbox(e);
					case ExpressionType.AddAssign: return dispatcher.DispatchAddAssign(e);
					case ExpressionType.AndAssign: return dispatcher.DispatchAndAssign(e);
					case ExpressionType.DivideAssign: return dispatcher.DispatchDivideAssign(e);
					case ExpressionType.ExclusiveOrAssign: return dispatcher.DispatchExclusiveOrAssign(e);
					case ExpressionType.LeftShiftAssign: return dispatcher.DispatchLeftShiftAssign(e);
					case ExpressionType.ModuloAssign: return dispatcher.DispatchModuloAssign(e);
					case ExpressionType.MultiplyAssign: return dispatcher.DispatchMultiplyAssign(e);
					case ExpressionType.OrAssign: return dispatcher.DispatchOrAssign(e);
					case ExpressionType.PowerAssign: return dispatcher.DispatchPowerAssign(e);
					case ExpressionType.RightShiftAssign: return dispatcher.DispatchRightShiftAssign(e);
					case ExpressionType.SubtractAssign: return dispatcher.DispatchSubtractAssign(e);
					case ExpressionType.AddAssignChecked: return dispatcher.DispatchAddAssignChecked(e);
					case ExpressionType.MultiplyAssignChecked: return dispatcher.DispatchMultiplyAssignChecked(e);
					case ExpressionType.SubtractAssignChecked: return dispatcher.DispatchSubtractAssignChecked(e);
					case ExpressionType.PreIncrementAssign: return dispatcher.DispatchPreIncrementAssign(e);
					case ExpressionType.PreDecrementAssign: return dispatcher.DispatchPreDecrementAssign(e);
					case ExpressionType.PostIncrementAssign: return dispatcher.DispatchPostIncrementAssign(e);
					case ExpressionType.PostDecrementAssign: return dispatcher.DispatchPostDecrementAssign(e);
					case ExpressionType.TypeEqual: return dispatcher.DispatchTypeEqual(e);
					case ExpressionType.OnesComplement: return dispatcher.DispatchOnesComplement(e);
					case ExpressionType.IsTrue: return dispatcher.DispatchIsTrue(e);
					case ExpressionType.IsFalse: return dispatcher.DispatchIsFalse(e);
					default:
						if(Enum.IsDefined(typeof(ExpressionType),e.NodeType))
							throw new NotImplementedException("ExpressionToCode supports .NET 3.5 expressions only");
						else
							throw new ArgumentOutOfRangeException("Impossible enum value:"+(int)e.NodeType);
				}
			} catch(NotImplementedException nie) {
				throw new ArgumentOutOfRangeException("Could not dispatch expr with nodetype "+e.NodeType+" and type " +e.GetType().Name,nie);
			}
		}
	}
}
 


<|start_filename|>ExpressionToCodeLib/Internal/SubExpressionPerLineCodeAnnotator.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace ExpressionToCodeLib.Internal
{
    class SubExpressionPerLineCodeAnnotator : ICodeAnnotator
    {
        public string AnnotateExpressionTree(ExpressionToCodeConfiguration config, Expression expr, string? msg, bool outerValueIsAssertionFailure)
            => (msg == null ? "" : msg + "\n\n") + ExpressionWithSubExpressions.Create(config, expr, outerValueIsAssertionFailure).ComposeToSingleString();

        struct ExpressionWithSubExpressions
        {
            const string spacedArrow = "   →   ";
            string ExpressionString;
            SubExpressionValue[] SubExpressions;

            public struct SubExpressionValue : IEquatable<SubExpressionValue>
            {
                public string SubExpression,
                    ValueAsString;

                public override int GetHashCode()
                    => SubExpression.GetHashCode() + 37 * ValueAsString.GetHashCode();

                public override bool Equals(object obj)
                    => obj is SubExpressionValue val && Equals(val);

                public bool Equals(SubExpressionValue val)
                    => SubExpression == val.SubExpression && ValueAsString == val.ValueAsString;
            }

            public static ExpressionWithSubExpressions Create(ExpressionToCodeConfiguration config, Expression e, bool outerValueIsAssertionFailure)
            {
                var sb = new StringBuilder();
                var ignoreInitialSpace = true;
                var node = new ExpressionToCodeImpl(config).ExpressionDispatch(e);
                AppendNodeToStringBuilder(sb, node, ref ignoreInitialSpace);
                var fullExprText = sb.ToString();
                var subExpressionValues = new List<SubExpressionValue>();
                FindSubExpressionValues(config, node, node, subExpressionValues, outerValueIsAssertionFailure);
                var assertionValue = outerValueIsAssertionFailure ? OutermostValue(config, node) : null;
                return new ExpressionWithSubExpressions {
                    ExpressionString = fullExprText
                        + (assertionValue != null ? "\n" + spacedArrow + assertionValue + " (caused assertion failure)\n" : ""),
                    SubExpressions = subExpressionValues.Distinct().ToArray()
                };
            }

            static string? OutermostValue(ExpressionToCodeConfiguration config, StringifiedExpression node)
            {
                if (node.OptionalValue != null) {
                    return ObjectToCodeImpl.ExpressionValueAsCode(config, node.OptionalValue, 10);
                }
                foreach (var kid in node.Children) {
                    if (!kid.IsConceptualChild) {
                        var value = OutermostValue(config, kid);
                        if (value != null) {
                            return value;
                        }
                    }
                }
                foreach (var kid in node.Children) {
                    if (kid.IsConceptualChild) {
                        var value = OutermostValue(config, kid);
                        if (value != null) {
                            return value;
                        }
                    }
                }
                return null;
            }

            static void AppendNodeToStringBuilder(StringBuilder sb, StringifiedExpression node, ref bool ignoreInitialSpace)
            {
                if (node.Text != null) {
                    var trimmedText = ignoreInitialSpace ? node.Text.TrimStart() : node.Text;
                    sb.Append(trimmedText);
                    ignoreInitialSpace = node.Text != "" && ExpressionToCode.ShouldIgnoreSpaceAfter(node.Text[node.Text.Length - 1]);
                } else {
                    foreach (var kid in node.Children) {
                        AppendNodeToStringBuilder(sb, kid, ref ignoreInitialSpace);
                    }
                }
            }

            static void FindSubExpressionValues(
                ExpressionToCodeConfiguration config,
                StringifiedExpression node,
                StringifiedExpression subExprNode,
                List<SubExpressionValue> subExpressionValues,
                bool outerValueIsAssertionFailure)
            {
                if (!outerValueIsAssertionFailure && node.OptionalValue != null) {
                    var sb = new StringBuilder();
                    var ignoreInitialSpace = true;
                    var valueString = ObjectToCodeImpl.ExpressionValueAsCode(config, node.OptionalValue, 10);
                    AppendNodeToStringBuilder(sb, subExprNode, ref ignoreInitialSpace);
                    var maxSize = Math.Max(40, config.Value.MaximumValueLength ?? 200);
                    var subExprString = sb.Length <= maxSize
                        ? sb.ToString()
                        : sb.ToString(0, maxSize / 2 - 1) + "  …  " + sb.ToString(sb.Length - (maxSize / 2 - 1), maxSize / 2 - 1);
                    // ReSharper disable once ReplaceWithStringIsNullOrEmpty - for nullability analysis
                    if (valueString != null && valueString != "") {
                        subExpressionValues.Add(new SubExpressionValue { SubExpression = subExprString, ValueAsString = valueString });
                    }
                }

                foreach (var kid in node.Children) {
                    if (!kid.IsConceptualChild) {
                        FindSubExpressionValues(config, kid, subExprNode, subExpressionValues, outerValueIsAssertionFailure);
                    }
                }

                foreach (var kid in node.Children) {
                    if (kid.IsConceptualChild) {
                        FindSubExpressionValues(config, kid, kid, subExpressionValues, false);
                    }
                }
            }

            public string ComposeToSingleString()
            {
                var maxLineLength = SubExpressions.Max(sub => sub.SubExpression.Length + spacedArrow.Length + sub.ValueAsString.Length as int?) ?? 0;
                var maxExprLength = SubExpressions.Max(sub => sub.SubExpression.Length as int?) ?? 0;
                var containsANewline = SubExpressions.Any(sub => sub.SubExpression.Contains("\n") || sub.ValueAsString.Contains("\n"));

                return ExpressionString + "\n"
                    + string.Join(
                        "",
                        maxLineLength <= 80 && maxExprLength <= 30 && !containsANewline
                            ? SubExpressions.Select(sub => sub.SubExpression.PadLeft(maxExprLength) + spacedArrow + sub.ValueAsString + "\n")
                            : SubExpressions.Select(sub => sub.SubExpression + "\n  " + spacedArrow + sub.ValueAsString + "\n\n")
                    );
            }
        }
    }
}


<|start_filename|>ExpressionToCodeLib/Internal/ExpressionPrecedence.cs<|end_filename|>
﻿using System;
using System.Linq.Expressions;

namespace ExpressionToCodeLib.Internal
{
    static class ExpressionPrecedence
    {
        static bool UnaryDashSym(ExpressionType et)
            => et == ExpressionType.Negate
                || et == ExpressionType.NegateChecked
                || et == ExpressionType.PreDecrementAssign;

        static bool UnaryPlusSym(ExpressionType et)
            => et == ExpressionType.UnaryPlus
                || et == ExpressionType.PreIncrementAssign;

        public static bool TokenizerConfusable(ExpressionType a, ExpressionType b)
            => UnaryDashSym(a) && UnaryDashSym(b) || UnaryPlusSym(a) && UnaryPlusSym(b);

        public static int Rank(ExpressionType exprType)
            => exprType switch {
                //brackets make no sense:
                ExpressionType.Block => -1,
                ExpressionType.Goto => -1,
                ExpressionType.Loop => -1,
                ExpressionType.Switch => -1,
                ExpressionType.Throw => -1,
                ExpressionType.Try => -1,
                ExpressionType.Label => -1,
                //brackets built-in; thus unnecesary (for params only!).
                ExpressionType.MemberInit => 1,
                ExpressionType.ArrayIndex => 1,
                ExpressionType.Call => 1,
                ExpressionType.Invoke => 1,
                ExpressionType.New => 1,
                ExpressionType.NewArrayInit => 1,
                ExpressionType.NewArrayBounds => 1,
                ExpressionType.ListInit => 1,
                ExpressionType.Power => 1, //non-native, uses Call.
                //other primary expressions
                ExpressionType.Constant => 1,
                ExpressionType.Parameter => 1,
                ExpressionType.MemberAccess => 1,
                ExpressionType.ArrayLength => 1,
                ExpressionType.Index => 1,
                ExpressionType.Default => 1,
                ExpressionType.PostIncrementAssign => 1,
                ExpressionType.PostDecrementAssign => 1,
                //unary prefixes
                ExpressionType.UnaryPlus => 2,
                ExpressionType.Negate => 2,
                ExpressionType.NegateChecked => 2,
                ExpressionType.Convert => 2,
                ExpressionType.ConvertChecked => 2,
                ExpressionType.Not => 2, //bitwise OR numeric!
                ExpressionType.OnesComplement => 2, //numeric
                ExpressionType.IsTrue => 2, //maybe?
                ExpressionType.IsFalse => 2, //maybe?
                ExpressionType.PreIncrementAssign => 2,
                ExpressionType.PreDecrementAssign => 2,
                //binary multiplicative
                ExpressionType.Modulo => 3,
                ExpressionType.Multiply => 3,
                ExpressionType.MultiplyChecked => 3,
                ExpressionType.Divide => 3,
                //binary addition
                ExpressionType.Add => 4,
                ExpressionType.AddChecked => 4,
                ExpressionType.Subtract => 4,
                ExpressionType.SubtractChecked => 4,
                ExpressionType.Decrement => 4, //nonnative; uses ... - 1
                ExpressionType.Increment => 4, //nonnative; uses ... - 1
                //binary shift
                ExpressionType.LeftShift => 5,
                ExpressionType.RightShift => 5,
                //relational excl. equals
                ExpressionType.LessThan => 6,
                ExpressionType.LessThanOrEqual => 6,
                ExpressionType.GreaterThan => 6,
                ExpressionType.GreaterThanOrEqual => 6,
                ExpressionType.TypeAs => 6,
                ExpressionType.TypeIs => 6,
                //equality
                ExpressionType.NotEqual => 7,
                ExpressionType.Equal => 7,
                //bitwise/eager
                ExpressionType.And => 8,
                ExpressionType.ExclusiveOr => 9,
                ExpressionType.Or => 10,
                //logical/shortcircuit:
                ExpressionType.AndAlso => 11,
                ExpressionType.OrElse => 12,
                //null-coalesce
                ExpressionType.Coalesce => 13,
                //ternary ? :
                ExpressionType.Conditional => 14,
                //assignments & lamba's
                ExpressionType.Lambda => 15,
                ExpressionType.Quote => 15, //maybe?
                ExpressionType.Assign => 15,
                ExpressionType.AddAssign => 15,
                ExpressionType.AndAssign => 15,
                ExpressionType.DivideAssign => 15,
                ExpressionType.ExclusiveOrAssign => 15,
                ExpressionType.LeftShiftAssign => 15,
                ExpressionType.ModuloAssign => 15,
                ExpressionType.MultiplyAssign => 15,
                ExpressionType.OrAssign => 15,
                ExpressionType.PowerAssign => 15,
                ExpressionType.RightShiftAssign => 15,
                ExpressionType.SubtractAssign => 15,
                ExpressionType.AddAssignChecked => 15,
                ExpressionType.MultiplyAssignChecked => 15,
                ExpressionType.SubtractAssignChecked => 15,
                _ => throw new ArgumentOutOfRangeException("Unsupported enum value:" + exprType)
            };
    }
}


<|start_filename|>ExpressionToCodeLib/Internal/ExpressionToCodeImpl.cs<|end_filename|>
﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;

namespace ExpressionToCodeLib.Internal
{
    class ExpressionToCodeImpl : IExpressionTypeDispatch<StringifiedExpression>
    {
        #region General Helpers
        public ExpressionToCodeImpl(ExpressionToCodeConfiguration config)
            => this.config = config;

        readonly ExpressionToCodeConfiguration config;
        IObjectStringifier objectStringifier => config.Value.ObjectStringifier;
        bool alwaysUseExplicitTypeArguments => config.Value.AlwaysUseExplicitTypeArguments;

        [Pure]
        IEnumerable<StringifiedExpression> NestExpression(ExpressionType? parentType, Expression child, bool parensIfEqualRank = false)
        {
            var parentRank = parentType == null ? 0 : ExpressionPrecedence.Rank(parentType.Value);
            var needsParens = parentRank > 0
                && (parensIfEqualRank ? parentRank - 1 : parentRank) < ExpressionPrecedence.Rank(child.NodeType);

            if (needsParens) {
                yield return StringifiedExpression.TextOnly("(");
            }

            yield return SingleChildDispatch(child);

            if (needsParens) {
                yield return StringifiedExpression.TextOnly(")");
            }
        }

        StringifiedExpression SingleChildDispatch(Expression child)
            => this.ExpressionDispatch(child).MarkAsConceptualChild();

        StringifiedExpression SingleChildDispatch(string? prefix, Expression child)
            => StringifiedExpression.WithChildren(new[] { StringifiedExpression.TextOnly(prefix), this.ExpressionDispatch(child) }).MarkAsConceptualChild();

        [Pure]
        static IEnumerable<StringifiedExpression> JoinDispatch(IEnumerable<StringifiedExpression> children, string joiner)
        {
            var isFirst = true;
            foreach (var child in children) {
                if (!isFirst) {
                    yield return StringifiedExpression.TextOnly(joiner);
                }

                yield return child;

                isFirst = false;
            }
        }

        [Pure]
        static IEnumerable<StringifiedExpression> ArgListDispatch(
            IEnumerable<StringifiedExpression> arguments,
            Expression? value = null,
            string open = "(",
            string close = ")",
            string joiner = ", ")
        {
            if (value != null) {
                yield return StringifiedExpression.TextAndExpr(open, value);
            } else {
                yield return StringifiedExpression.TextOnly(open);
            }

            foreach (var o in JoinDispatch(arguments, joiner)) {
                yield return o;
            }

            yield return StringifiedExpression.TextOnly(close);
        }

        struct KidsBuilder
        {
            readonly List<StringifiedExpression> kids;

            KidsBuilder(List<StringifiedExpression> init)
                => kids = init;

            [Pure]
            public static KidsBuilder Create()
                => new KidsBuilder(new List<StringifiedExpression>());

            public void Add(StringifiedExpression node)
                => kids.Add(node);

            public void Add(IEnumerable<StringifiedExpression> nodes)
                => kids.AddRange(nodes);

            public void Add(string text, Expression value)
                => kids.Add(StringifiedExpression.TextAndExpr(text, value));

            public void Add(string text)
                => kids.Add(StringifiedExpression.TextOnly(text));

            public StringifiedExpression Finish()
                => StringifiedExpression.WithChildren(kids.ToArray());
        }

        [Pure]
        StringifiedExpression BinaryDispatch(string op, Expression e)
        {
            var kids = KidsBuilder.Create();
            var be = (BinaryExpression)e;
            UnwrapEnumOp(be, out var left, out var right);
            kids.Add(NestExpression(be.NodeType, left));
            kids.Add(" " + op + " ", e);
            kids.Add(NestExpression(be.NodeType, right, true));
            return kids.Finish();
        }

        [Pure]
        static void UnwrapEnumOp(BinaryExpression be, out Expression left, out Expression right)
        {
            left = be.Left;
            right = be.Right;
            var uleft = left.NodeType == ExpressionType.Convert ? ((UnaryExpression)left).Operand : null;
            var uright = right.NodeType == ExpressionType.Convert ? ((UnaryExpression)right).Operand : null;
            if (uleft != null) {
                if (uright != null) {
                    if (uright.Type.EnusureNullability() == uleft.Type.EnusureNullability()) {
                        left = uleft;
                        right = uright;
                    }
                } else {
                    UnwrapEnumBinOp(uleft, ref left, ref right);
                }
            } else if (uright != null) {
                //implies uleft == null
                UnwrapEnumBinOp(uright, ref right, ref left);
            }
        }

        static void UnwrapEnumBinOp(Expression expr1uncast, ref Expression expr1, ref Expression expr2)
        {
            var expr1nonnullableType = expr1uncast.Type.AvoidNullability();
            var expr2nonnullableType = expr2.Type.AvoidNullability();
            if (expr1nonnullableType.GetTypeInfo().IsEnum
                && expr1nonnullableType.GetTypeInfo().GetEnumUnderlyingType() == expr2nonnullableType
                || expr1nonnullableType == typeof(char) && expr2nonnullableType == typeof(int)
            ) {
                expr1 = expr1uncast;

                if (expr2.NodeType == ExpressionType.Constant) {
                    var value = ((ConstantExpression)expr2).Value;
                    if (value == null) {
                        expr2 = Expression.Default(expr1uncast.Type.EnusureNullability());
                    } else if (expr1nonnullableType == typeof(char)) {
                        expr2 = Expression.Constant((char)(int)value);
                    } else {
                        expr2 = Expression.Constant(Enum.ToObject(expr1nonnullableType, value));
                    }
                } else {
                    expr2 = Expression.Convert(expr2, expr1uncast.Type);
                }
            }
        }

        [Pure]
        StringifiedExpression UnaryDispatch(string op, Expression e)
        {
            var ue = (UnaryExpression)e;
            var needsSpace = ExpressionPrecedence.TokenizerConfusable(ue.NodeType, ue.Operand.NodeType);
            var kids = KidsBuilder.Create();
            kids.Add(op + (needsSpace ? " " : ""), e);
            kids.Add(NestExpression(ue.NodeType, ue.Operand));
            return kids.Finish();
        }

        [Pure]
        StringifiedExpression UnaryDispatchConvert(Expression e)
        {
            var kids = KidsBuilder.Create();
            var ue = (UnaryExpression)e;
            var operand = ue.Operand;
            if (!config.Value.OmitImplicitCasts
                || !ReflectionHelpers.CanImplicitlyCast(operand.Type, e.Type)
                || typeof(Delegate).IsAssignableFrom(operand.Type) && operand.NodeType == ExpressionType.Lambda
                || typeof(Expression).IsAssignableFrom(operand.Type) && operand.NodeType == ExpressionType.Quote
            ) {
                if (e.Type.GetTypeInfo().IsAssignableFrom(operand.Type)) // base class, basically; don't re-print identical values.
                {
                    kids.Add("(" + objectStringifier.TypeNameToCode(e.Type) + ")");
                } else {
                    kids.Add("(" + objectStringifier.TypeNameToCode(e.Type) + ")", e);
                }

                if (operand.NodeType == ExpressionType.Convert) {
                    var nestedConvert = (UnaryExpression)operand;
                    kids.Add("(" + objectStringifier.TypeNameToCode(operand.Type) + ")");
                    kids.Add(NestExpression(nestedConvert.NodeType, nestedConvert.Operand));
                } else {
                    kids.Add(NestExpression(ue.NodeType, operand));
                }
            } else {
                kids.Add(NestExpression(ue.NodeType, operand));
            }

            return kids.Finish();
        }

        [Pure]
        StringifiedExpression UnaryPostfixDispatch(string op, Expression e)
        {
            var kids = KidsBuilder.Create();
            var ue = (UnaryExpression)e;
            kids.Add(NestExpression(ue.NodeType, ue.Operand));
            kids.Add(op, e);
            return kids.Finish();
        }

        [Pure]
        StringifiedExpression TypeOpDispatch(string op, Expression e)
        {
            var kids = KidsBuilder.Create();
            kids.Add(NestExpression(e.NodeType, ((TypeBinaryExpression)e).Expression));
            kids.Add(" " + op + " ", e);
            kids.Add(objectStringifier.TypeNameToCode(((TypeBinaryExpression)e).TypeOperand));
            return kids.Finish();
        }

        [Pure]
        StringifiedExpression StatementDispatch(Expression e, ExpressionType? parentType = null)
        {
            var kids = KidsBuilder.Create();
            kids.Add(NestExpression(parentType, e));
            kids.Add("; ");
            return kids.Finish();
        }

        [Pure]
        StringifiedExpression StatementDispatch(string prefix, Expression e, ExpressionType? parentType = null)
        {
            var kids = KidsBuilder.Create();
            kids.Add(prefix);
            kids.Add(" ");
            kids.Add(StatementDispatch(e, parentType));
            return kids.Finish();
        }
        #endregion

        #region Hard Cases
        [Pure]
        public StringifiedExpression DispatchLambda(Expression e)
        {
            var kids = KidsBuilder.Create();
            var le = (LambdaExpression)e;
            kids.Add(
                le.Parameters.Count == 1
                    ? NestExpression(e.NodeType, le.Parameters.Single())
                    : ArgListDispatch(le.Parameters.Select(SingleChildDispatch))
                //though delegate lambdas do support ref/out parameters, expression tree lambda's don't
            );
            kids.Add(" => ");
            kids.Add(NestExpression(le.NodeType, le.Body));
            return kids.Finish();
        }

        static bool isThisRef(Expression e)
            => e.NodeType == ExpressionType.Constant
                && ((ConstantExpression)e).Value != null
                && e.Type.GuessTypeClass() == ReflectionHelpers.TypeClass.NormalType;

        static bool isClosureRef(Expression e)
            => (
                    e.NodeType == ExpressionType.Constant && ((ConstantExpression)e).Value != null
                    || e.NodeType == ExpressionType.MemberAccess
                )
                && e.Type.GuessTypeClass() is { } typeClass
                && (typeClass == ReflectionHelpers.TypeClass.ClosureType || typeClass == ReflectionHelpers.TypeClass.TopLevelProgramClosureType);

        [Pure]
        public StringifiedExpression DispatchMemberAccess(Expression e)
        {
            var kids = KidsBuilder.Create();
            var me = (MemberExpression)e;
            var memberOfExpr = me.Expression;
            if (memberOfExpr != null && !isThisRef(memberOfExpr) && !isClosureRef(memberOfExpr)) {
                kids.Add(NestExpression(e.NodeType, memberOfExpr));
                kids.Add(".");
            } else if (ReflectionHelpers.IsMemberInfoStatic(me.Member)) {
                kids.Add(objectStringifier.TypeNameToCode(me.Member.DeclaringType) + ".");
            }

            kids.Add(me.Member.Name, e);
            return kids.Finish();
        }

        static readonly MethodInfo createDelegate = ((Func<Type, object, MethodInfo, Delegate>)Delegate.CreateDelegate).Method;

        [Pure]
        public StringifiedExpression DispatchCall(Expression e)
        {
            var kids = KidsBuilder.Create();

            var mce = (MethodCallExpression)e;

            var optPropertyInfo = ReflectionHelpers.GetPropertyIfGetter(mce.Method);
            if (optPropertyInfo != null
                && (optPropertyInfo.Name == "Item"
                    || mce.Object?.Type == typeof(string) && optPropertyInfo.Name == "Chars")) {
                kids.Add(NestExpression(mce.NodeType, mce.Object));
                //indexers don't support ref/out; so we can use unprefixed arguments
                kids.Add(ArgListDispatch(GetArgumentsForMethod(mce.Method, mce.Arguments), mce, "[", "]"));
            } else if (mce.Method.Equals(createDelegate) && mce.Arguments.Count == 3
                && mce.Arguments[2].NodeType == ExpressionType.Constant && mce.Arguments[2].Type == typeof(MethodInfo)) {
                //.net 4.0
                //implicitly constructed delegate from method group.
                var targetMethod = (MethodInfo)((ConstantExpression)mce.Arguments[2]).Value;
                var targetExpr = mce.Arguments[1].NodeType == ExpressionType.Constant
                    && ((ConstantExpression)mce.Arguments[1]).Value == null
                        ? null
                        : mce.Arguments[1];
                kids.Add(StringifyMethodName(mce, targetMethod, targetExpr));
            } else if (mce.Method.Name == "CreateDelegate"
                && mce.Arguments.Count == 2
                && mce.Object?.Type == typeof(MethodInfo)
                && mce.Object.NodeType == ExpressionType.Constant
                && mce.Method.GetParameters()[1].ParameterType == typeof(object)
            ) {
                //.net 4.5
                //implicitly constructed delegate from method group.
                var targetMethod = (MethodInfo)((ConstantExpression)mce.Object).Value;
                var targetExpr = mce.Arguments[1].NodeType == ExpressionType.Constant
                    && ((ConstantExpression)mce.Arguments[1]).Value == null
                        ? null
                        : mce.Arguments[1];
                kids.Add(StringifyMethodName(mce, targetMethod, targetExpr));
            } else if (mce.Object == null
                && mce.Type.FullName == "System.FormattableString"
                && mce.Method.DeclaringType?.FullName == "System.Runtime.CompilerServices.FormattableStringFactory"
                && mce.Method.Name == "Create"
                && mce.Arguments.Count == 2
                && mce.Arguments[0] is ConstantExpression formattableStringFormat
                && mce.Arguments[1] is NewArrayExpression formattableStringArguments
            ) {
                //.net 4.6
                //string-interpolations are compiled into FormattableStringFactory.Create
                var formatString = (string)formattableStringFormat.Value;
                var arguments = formattableStringArguments.Expressions;
                kids = AddStringInterpolation(kids, formatString, arguments);
            } else if (mce.Object == null
                && mce.Type == typeof(string)
                && mce.Method.DeclaringType == typeof(string)
                && mce.Method.Name == "Format"
                && mce.Arguments.Count == 2
                && mce.Arguments[0] is ConstantExpression formatStringExpr
                && formatStringExpr.Type == typeof(string)
                && mce.Arguments[1].Type == typeof(object[])
                && mce.Arguments[1] is NewArrayExpression interpolationArguments
            ) {
                //.net 4.6
                //string-interpolations are compiled into FormattableStringFactory.Create
                var formatString = (string)formatStringExpr.Value;
                var arguments = interpolationArguments.Expressions;
                kids = AddStringInterpolation(kids, formatString, arguments);
            } else if (mce.Object == null
                && mce.Type == typeof(string)
                && mce.Method.DeclaringType == typeof(string)
                && mce.Method.Name == "Format"
                && mce.Arguments.Count >= 2
                && mce.Arguments[0] is ConstantExpression formatString2Expr
                && formatString2Expr.Type == typeof(string)
                && mce.Method.GetParameters().Skip(1).All(pi => pi.ParameterType == typeof(object))
            ) {
                //.net 4.6
                //string-interpolations are compiled into FormattableStringFactory.Create
                var formatString = (string)formatString2Expr.Value;
                var arguments = mce.Arguments.Skip(1);
                kids = AddStringInterpolation(kids, formatString, arguments);
            } else if (mce.Object != null && mce.Method.Attributes.HasFlag(MethodAttributes.SpecialName) && mce.Method.Name == "get_Item") {
                //.net 4.5.1 or older object indexer.

                kids.Add(NestExpression(mce.NodeType, mce.Object));
                kids.Add(ArgListDispatch(mce.Arguments.Select(SingleChildDispatch), mce, "[", "]"));
                return kids.Finish();
            } else {
                var isExtensionMethod = mce.Method.IsStatic
                    && mce.Method.GetCustomAttributes(typeof(ExtensionAttribute), false).Any() && mce.Arguments.Any()
                    && mce.Object == null;
                var objectExpr = isExtensionMethod ? mce.Arguments.First() : mce.Object;
                kids.Add(StringifyMethodName(mce, mce.Method, objectExpr));
                var args = GetArgumentsForMethod(mce.Method, mce.Arguments);

                kids.Add(ArgListDispatch(isExtensionMethod ? args.Skip(1) : args));
            }

            return kids.Finish();
        }

        KidsBuilder AddStringInterpolation(KidsBuilder kids, string formatString, IEnumerable<Expression> arguments)
        {
            var interpolationArgumentsStringified = arguments
                .Select(
                    expr =>
                        expr.NodeType == ExpressionType.Convert
                        && expr.Type == typeof(object)
                        && expr is UnaryExpression unaryExpr
                            ? unaryExpr.Operand
                            : expr
                )
                .Select(
                    child =>
                        child.NodeType == ExpressionType.Conditional
                            ? StringifiedExpression.WithChildren(new[] { StringifiedExpression.TextOnly("("), this.ExpressionDispatch(child), StringifiedExpression.TextOnly(")") })
                            : this.ExpressionDispatch(child)
                ).ToArray();
            var useLiteralSyntax = ObjectStringifyImpl.PreferLiteralSyntax(formatString)
                || StringifiedExpression.WithChildren(interpolationArgumentsStringified).ToString().Contains("\n");

            var parsed = FormatStringParser.ParseFormatString(formatString, interpolationArgumentsStringified.Cast<object>().ToArray());

            if (useLiteralSyntax) {
                kids.Add("$@\"");
                foreach (var segment in parsed.segments) {
                    kids.Add(segment.InitialStringPart.Replace("\"", "\"\"").Replace("{", "{{").Replace("}", "}}") + "{");
                    kids.Add((StringifiedExpression)segment.FollowedByValue);
                    if (segment.WithFormatString != null) {
                        kids.Add(":" + segment.WithFormatString + "}");
                    } else {
                        kids.Add("}");
                    }
                }

                kids.Add(parsed.Tail.Replace("\"", "\"\"").Replace("{", "{{").Replace("}", "}}") + "\"");
            } else {
                kids.Add("$\"");
                foreach (var segment in parsed.segments) {
                    kids.Add(ObjectStringifyImpl.EscapeStringChars(segment.InitialStringPart.Replace("{", "{{").Replace("}", "}}")) + "{");
                    kids.Add((StringifiedExpression)segment.FollowedByValue);
                    if (segment.WithFormatString != null) {
                        kids.Add(":" + segment.WithFormatString + "}");
                    } else {
                        kids.Add("}");
                    }
                }

                kids.Add(ObjectStringifyImpl.EscapeStringChars(parsed.Tail.Replace("{", "{{").Replace("}", "}}")) + "\"");
            }

            return kids;
        }

        IEnumerable<StringifiedExpression> GetArgumentsForMethod(MethodBase methodInfo, IEnumerable<Expression> argValueExprs)
            => GetArgumentsForMethod(methodInfo.GetParameters(), argValueExprs);

        IEnumerable<StringifiedExpression> GetArgumentsForMethod(ParameterInfo[] parameters, IEnumerable<Expression> argValueExprs)
        {
            var argPrefixes = parameters.Select(p => p.IsOut ? "out " : p.ParameterType.IsByRef ? "ref " : null).ToArray();
            return argValueExprs.Zip(argPrefixes, (expr, prefix) => SingleChildDispatch(prefix, expr));
        }

        [Pure]
        StringifiedExpression StringifyMethodName(MethodCallExpression mce, MethodInfo method, Expression? objExpr)
        {
            var kids = KidsBuilder.Create();

            if (objExpr != null) {
                if (!(isThisRef(objExpr) || isClosureRef(objExpr))) {
                    kids.Add(NestExpression(mce.NodeType, objExpr));
                    kids.Add(".");
                }
            } else if (method.IsStatic) {
                kids.Add(objectStringifier.TypeNameToCode(method.DeclaringType) + "."); //TODO:better reference avoiding for this?
            }

            var methodName = method.Name;

            methodName += CreateGenericArgumentsIfNecessary(method);
            kids.Add(methodName, mce);
            return kids.Finish();
        }

        string CreateGenericArgumentsIfNecessary(MethodInfo method)
        {
            if (!method.IsGenericMethod) {
                return "";
            }

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

                var confusibleOverloads = method.DeclaringType.GetTypeInfo()
                    .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 "";
                }
            }

            var methodTypeArgs = method.GetGenericArguments().Select(type => objectStringifier.TypeNameToCode(type)).ToArray();
            return string.Concat("<", string.Join(", ", methodTypeArgs), ">");
        }

        static bool ContainsInferableType(Type haystack, Type needle)
            => haystack == needle
                || (haystack.IsArray || haystack.IsByRef) && ContainsInferableType(haystack.GetElementType() ?? throw new InvalidOperationException("arrays have elements"), needle)
                || haystack.GetTypeInfo().IsGenericType
                && !(typeof(Delegate).IsAssignableFrom(haystack)
                        && (haystack.GetTypeInfo().GetMethod("Invoke") ?? throw new InvalidOperationException("delegates have Invoke")).GetParameters().Any(pi => pi.ParameterType == needle)
                    )
                && haystack.GetTypeInfo().GetGenericArguments().Any(argType => ContainsInferableType(argType, needle));

        [Pure]
        public StringifiedExpression DispatchIndex(Expression e)
        {
            var kids = KidsBuilder.Create();

            var ie = (IndexExpression)e;
            kids.Add(NestExpression(ie.NodeType, ie.Object));

            var args = ie.Indexer == null
                ? ie.Arguments.Select(SingleChildDispatch)
                : GetArgumentsForMethod(ie.Indexer.GetIndexParameters(), ie.Arguments);

            kids.Add(ArgListDispatch(args, ie, "[", "]"));
            return kids.Finish();
        }

        [Pure]
        public StringifiedExpression DispatchInvoke(Expression e)
        {
            var kids = KidsBuilder.Create();

            var ie = (InvocationExpression)e;
            if (ie.Expression.NodeType == ExpressionType.Lambda) {
                kids.Add("new " + objectStringifier.TypeNameToCode(ie.Expression.Type));
            }

            kids.Add(NestExpression(ie.NodeType, ie.Expression));
            var invokeMethod = ie.Expression.Type.GetTypeInfo().GetMethod("Invoke");
            var args = GetArgumentsForMethod(invokeMethod, ie.Arguments);
            kids.Add(ArgListDispatch(args, ie));
            return kids.Finish();
        }

        [Pure]
        public StringifiedExpression DispatchConstant(Expression e)
        {
            var kids = KidsBuilder.Create();

            var const_Val = ((ConstantExpression)e).Value;
            var codeRepresentation = objectStringifier.PlainObjectToCode(const_Val, e.Type);
            //e.Type.IsVisible
            if (codeRepresentation == null) {
                var typeclass = e.Type.GuessTypeClass();
                if (typeclass == ReflectionHelpers.TypeClass.NormalType) // probably this!
                {
                    kids.Add("this"); //TODO:verify that all this references refer to the same object!
                } else {
                    throw new ArgumentOutOfRangeException(
                        nameof(e),
                        "Can't print constant " + (const_Val?.ToString() ?? "<null>")
                        + " in expr of type " + e.Type
                    );
                }
            } else {
                kids.Add(codeRepresentation);
            }

            return kids.Finish();
        }

        [Pure]
        public StringifiedExpression DispatchConditional(Expression e)
        {
            var kids = KidsBuilder.Create();

            var ce = (ConditionalExpression)e;
            kids.Add(NestExpression(ce.NodeType, ce.Test));
            kids.Add(" ? ", e);
            kids.Add(NestExpression(ce.NodeType, ce.IfTrue));
            kids.Add(" : ");
            kids.Add(NestExpression(ce.NodeType, ce.IfFalse));
            return kids.Finish();
        }

        [Pure]
        public StringifiedExpression DispatchListInit(Expression e)
        {
            var kids = KidsBuilder.Create();

            var lie = (ListInitExpression)e;
            kids.Add("new ", lie);
            kids.Add(objectStringifier.TypeNameToCode(lie.NewExpression.Constructor.DeclaringType));
            if (lie.NewExpression.Arguments.Any()) {
                kids.Add(ArgListDispatch(GetArgumentsForMethod(lie.NewExpression.Constructor, lie.NewExpression.Arguments)));
            }

            kids.Add(" { ");
            kids.Add(JoinDispatch(lie.Initializers.Select(DispatchElementInit), ", "));
            kids.Add(" }");
            return kids.Finish();
        }

        [Pure]
        StringifiedExpression DispatchElementInit(ElementInit elemInit)
        {
            if (elemInit.Arguments.Count != 1) {
                return StringifiedExpression.WithChildren(ArgListDispatch(elemInit.Arguments.Select(SingleChildDispatch), null, "{ ", " }").ToArray()); //??
            } else {
                return SingleChildDispatch(elemInit.Arguments.Single());
            }
        }

        [Pure]
        StringifiedExpression DispatchMemberBinding(MemberBinding mb)
        {
            var kids = KidsBuilder.Create();

            kids.Add(mb.Member.Name + " = ");
            if (mb is MemberMemberBinding mmb) {
                kids.Add("{ ");
                kids.Add(JoinDispatch(mmb.Bindings.Select(DispatchMemberBinding), ", "));
                kids.Add(" }");
            } else if (mb is MemberListBinding mlb) {
                kids.Add("{ ");
                kids.Add(JoinDispatch(mlb.Initializers.Select(DispatchElementInit), ", "));
                kids.Add(" }");
            } else if (mb is MemberAssignment assignment) {
                kids.Add(SingleChildDispatch(assignment.Expression));
            } else {
                throw new NotImplementedException("Member binding of unknown type: " + mb.GetType());
            }

            return kids.Finish();
        }

        [Pure]
        public StringifiedExpression DispatchMemberInit(Expression e)
        {
            var kids = KidsBuilder.Create();

            var mie = (MemberInitExpression)e;
            kids.Add("new ", mie);
            kids.Add(objectStringifier.TypeNameToCode(mie.NewExpression.Constructor.DeclaringType));
            if (mie.NewExpression.Arguments.Any()) {
                kids.Add(ArgListDispatch(GetArgumentsForMethod(mie.NewExpression.Constructor, mie.NewExpression.Arguments)));
            }

            kids.Add(" { ");
            kids.Add(JoinDispatch(mie.Bindings.Select(DispatchMemberBinding), ", "));
            kids.Add(" }");
            return kids.Finish();
        }

        [Pure]
        public StringifiedExpression DispatchNew(Expression e)
        {
            var kids = KidsBuilder.Create();

            var ne = (NewExpression)e;
            if (ne.Type.GuessTypeClass() == ReflectionHelpers.TypeClass.AnonymousType) {
                var parms = ne.Type.GetTypeInfo().GetConstructors().Single().GetParameters();
                var props = ne.Type.GetTypeInfo().GetProperties();
                if (
                    !parms.Select(p => new { p.Name, Type = p.ParameterType })
                        .SequenceEqual(props.Select(p => new { p.Name, Type = p.PropertyType }))) {
                    throw new InvalidOperationException(
                        "Constructor params for anonymous type don't match it's properties!"
                    );
                }

                if (!parms.Select(p => p.ParameterType).SequenceEqual(ne.Arguments.Select(argE => argE.Type))) {
                    throw new InvalidOperationException(
                        "Constructor Arguments for anonymous type don't match it's type signature!"
                    );
                }

                kids.Add("new { ");
                for (var i = 0; i < props.Length; i++) {
                    kids.Add(props[i].Name + " = ");
                    kids.Add(SingleChildDispatch(ne.Arguments[i]));
                    if (i + 1 < props.Length) {
                        kids.Add(", ");
                    }
                }

                kids.Add(" }");
            } else {
                kids.Add("new " + objectStringifier.TypeNameToCode(ne.Type), ne);
                kids.Add(ArgListDispatch(GetArgumentsForMethod(ne.Constructor, ne.Arguments)));
            }

            //TODO: deal with anonymous types.
            return kids.Finish();
        }

        [Pure]
        public StringifiedExpression DispatchNewArrayInit(Expression e)
        {
            var kids = KidsBuilder.Create();

            var nae = (NewArrayExpression)e;
            var arrayElemType = nae.Type.GetElementType();
            var isDelegate = typeof(Delegate).GetTypeInfo().IsAssignableFrom(arrayElemType);
            var implicitTypeOK = !isDelegate && nae.Expressions.Any()
                && nae.Expressions.All(expr => expr.Type == arrayElemType);
            kids.Add("new" + (implicitTypeOK ? "" : " " + objectStringifier.TypeNameToCode(arrayElemType)) + "[] ", nae);
            kids.Add(ArgListDispatch(nae.Expressions.Select(SingleChildDispatch), null, "{ ", " }"));
            return kids.Finish();
        }

        [Pure]
        public StringifiedExpression DispatchNewArrayBounds(Expression e)
        {
            var kids = KidsBuilder.Create();

            var nae = (NewArrayExpression)e;
            var arrayElemType = nae.Type.GetElementType();
            kids.Add("new " + objectStringifier.TypeNameToCode(arrayElemType), nae);
            kids.Add(ArgListDispatch(nae.Expressions.Select(SingleChildDispatch), null, "[", "]"));
            return kids.Finish();
        }

        [Pure]
        public StringifiedExpression DispatchBlock(Expression e)
        {
            var kids = KidsBuilder.Create();

            var be = (BlockExpression)e;
            var hasReturn = be.Type != typeof(void);
            var statements = hasReturn ? be.Expressions.Take(be.Expressions.Count - 1) : be.Expressions;

            kids.Add("{ ");

            foreach (var v in be.Variables) {
                kids.Add(StatementDispatch(objectStringifier.TypeNameToCode(v.Type), v, ExpressionType.Block));
            }

            foreach (var child in statements) {
                kids.Add(StatementDispatch(child, ExpressionType.Block));
            }

            if (hasReturn) {
                kids.Add(StatementDispatch("return", be.Result, ExpressionType.Block));
            }

            kids.Add("}");
            return kids.Finish();
        }
        #endregion

        #region Easy Cases
        [Pure]
        public StringifiedExpression DispatchPower(Expression e)
        {
            var kids = KidsBuilder.Create();

            kids.Add("Math.Pow", e);
            var binaryExpression = (BinaryExpression)e;
            kids.Add(ArgListDispatch(new[] { binaryExpression.Left, binaryExpression.Right }.Select(SingleChildDispatch)));
            return kids.Finish();
        }

        [Pure]
        public StringifiedExpression DispatchAdd(Expression e)
            => BinaryDispatch("+", e);

        [Pure]
        public StringifiedExpression DispatchAddChecked(Expression e)
            => BinaryDispatch("+", e); //TODO: checked

        [Pure]
        public StringifiedExpression DispatchAnd(Expression e)
            => BinaryDispatch("&", e);

        [Pure]
        public StringifiedExpression DispatchAndAlso(Expression e)
            => BinaryDispatch("&&", e);

        [Pure]
        public StringifiedExpression DispatchArrayLength(Expression e)
        {
            var kids = KidsBuilder.Create();

            kids.Add(NestExpression(e.NodeType, ((UnaryExpression)e).Operand));
            kids.Add(".Length", e);
            return kids.Finish();
        }

        [Pure]
        public StringifiedExpression DispatchArrayIndex(Expression e)
        {
            var kids = KidsBuilder.Create();

            var binaryExpression = (BinaryExpression)e;
            kids.Add(NestExpression(e.NodeType, binaryExpression.Left));
            kids.Add("[", e);
            kids.Add(NestExpression(null, binaryExpression.Right));
            kids.Add("]");
            return kids.Finish();
        }

        [Pure]
        public StringifiedExpression DispatchCoalesce(Expression e)
            => ((BinaryExpression)e).Left is ConstantExpression ce && ((BinaryExpression)e).Right is ConstantExpression && ce.Type == e.Type && ce.Type == typeof(string) && ce.Value != null
                ? this.ExpressionDispatch(ce) //for some weird reason the compile sometimes generates redundant null-coalescing operators.  Get rid of em!
                : BinaryDispatch("??", e);

        [Pure]
        public StringifiedExpression DispatchConvert(Expression e)
            => UnaryDispatchConvert(e);

        [Pure]
        public StringifiedExpression DispatchConvertChecked(Expression e)
            => UnaryDispatchConvert(e);

        //TODO: get explicit and implicit conversion operators right.
        [Pure]
        public StringifiedExpression DispatchDivide(Expression e)
            => BinaryDispatch("/", e);

        [Pure]
        public StringifiedExpression DispatchEqual(Expression e)
            => BinaryDispatch("==", e);

        [Pure]
        public StringifiedExpression DispatchExclusiveOr(Expression e)
            => BinaryDispatch("^", e);

        [Pure]
        public StringifiedExpression DispatchGreaterThan(Expression e)
            => BinaryDispatch(">", e);

        [Pure]
        public StringifiedExpression DispatchGreaterThanOrEqual(Expression e)
            => BinaryDispatch(">=", e);

        [Pure]
        public StringifiedExpression DispatchLeftShift(Expression e)
            => BinaryDispatch("<<", e);

        [Pure]
        public StringifiedExpression DispatchLessThan(Expression e)
            => BinaryDispatch("<", e);

        [Pure]
        public StringifiedExpression DispatchLessThanOrEqual(Expression e)
            => BinaryDispatch("<=", e);

        [Pure]
        public StringifiedExpression DispatchModulo(Expression e)
            => BinaryDispatch("%", e);

        [Pure]
        public StringifiedExpression DispatchMultiply(Expression e)
            => BinaryDispatch("*", e);

        [Pure]
        public StringifiedExpression DispatchMultiplyChecked(Expression e)
            => BinaryDispatch("*", e);

        [Pure]
        public StringifiedExpression DispatchNegate(Expression e)
            => UnaryDispatch("-", e);

        [Pure]
        public StringifiedExpression DispatchUnaryPlus(Expression e)
            => UnaryDispatch("+", e);

        [Pure]
        public StringifiedExpression DispatchNegateChecked(Expression e)
            => UnaryDispatch("-", e);

        [Pure]
        public StringifiedExpression DispatchNot(Expression e)
            => UnaryDispatch(e.Type == typeof(bool) || e.Type == typeof(bool?) ? "!" : "~", e);

        [Pure]
        public StringifiedExpression DispatchNotEqual(Expression e)
            => BinaryDispatch("!=", e);

        [Pure]
        public StringifiedExpression DispatchOr(Expression e)
            => BinaryDispatch("|", e);

        [Pure]
        public StringifiedExpression DispatchOrElse(Expression e)
            => BinaryDispatch("||", e);

        [Pure]
        public StringifiedExpression DispatchParameter(Expression e)
        {
            var parameterExpression = (ParameterExpression)e;
            // ReSharper disable once ConstantNullCoalescingCondition
            return StringifiedExpression.TextAndExpr(parameterExpression.Name ?? parameterExpression.Type.Name + parameterExpression.GetHashCode(), e);
        }

        [Pure]
        public StringifiedExpression DispatchQuote(Expression e)
            => StringifiedExpression.WithChildren(NestExpression(e.NodeType, ((UnaryExpression)e).Operand).ToArray());

        [Pure]
        public StringifiedExpression DispatchRightShift(Expression e)
            => BinaryDispatch(">>", e);

        [Pure]
        public StringifiedExpression DispatchSubtract(Expression e)
            => BinaryDispatch("-", e);

        [Pure]
        public StringifiedExpression DispatchSubtractChecked(Expression e)
            => BinaryDispatch("-", e);

        [Pure]
        public StringifiedExpression DispatchTypeAs(Expression e)
            => UnaryPostfixDispatch(" as " + objectStringifier.TypeNameToCode(e.Type), e);

        [Pure]
        public StringifiedExpression DispatchTypeIs(Expression e)
            => TypeOpDispatch("is", e);

        [Pure]
        public StringifiedExpression DispatchAssign(Expression e)
            => BinaryDispatch("=", e);

        [Pure]
        public StringifiedExpression DispatchDecrement(Expression e)
            => UnaryPostfixDispatch(" - 1", e);

        [Pure]
        public StringifiedExpression DispatchIncrement(Expression e)
            => UnaryPostfixDispatch(" + 1", e);

        [Pure]
        public StringifiedExpression DispatchAddAssign(Expression e)
            => BinaryDispatch("+=", e);

        [Pure]
        public StringifiedExpression DispatchAndAssign(Expression e)
            => BinaryDispatch("&=", e);

        [Pure]
        public StringifiedExpression DispatchDivideAssign(Expression e)
            => BinaryDispatch("/=", e);

        [Pure]
        public StringifiedExpression DispatchExclusiveOrAssign(Expression e)
            => BinaryDispatch("^=", e);

        [Pure]
        public StringifiedExpression DispatchLeftShiftAssign(Expression e)
            => BinaryDispatch("<<=", e);

        [Pure]
        public StringifiedExpression DispatchModuloAssign(Expression e)
            => BinaryDispatch("%=", e);

        [Pure]
        public StringifiedExpression DispatchMultiplyAssign(Expression e)
            => BinaryDispatch("*=", e);

        [Pure]
        public StringifiedExpression DispatchOrAssign(Expression e)
            => BinaryDispatch("|=", e);

        [Pure]
        public StringifiedExpression DispatchRightShiftAssign(Expression e)
            => BinaryDispatch(">>=", e);

        [Pure]
        public StringifiedExpression DispatchSubtractAssign(Expression e)
            => BinaryDispatch("-=", e);

        [Pure]
        public StringifiedExpression DispatchAddAssignChecked(Expression e)
            => BinaryDispatch("+=", e);

        [Pure]
        public StringifiedExpression DispatchMultiplyAssignChecked(Expression e)
            => BinaryDispatch("*=", e);

        [Pure]
        public StringifiedExpression DispatchSubtractAssignChecked(Expression e)
            => BinaryDispatch("-=", e);

        [Pure]
        public StringifiedExpression DispatchPreIncrementAssign(Expression e)
            => UnaryDispatch("++", e);

        [Pure]
        public StringifiedExpression DispatchPreDecrementAssign(Expression e)
            => UnaryDispatch("--", e);

        [Pure]
        public StringifiedExpression DispatchPostIncrementAssign(Expression e)
            => UnaryPostfixDispatch("++", e);

        [Pure]
        public StringifiedExpression DispatchPostDecrementAssign(Expression e)
            => UnaryPostfixDispatch("--", e);

        [Pure]
        public StringifiedExpression DispatchOnesComplement(Expression e)
            => UnaryDispatch("~", e);
        #endregion

        #region Unused by C#'s expression support; or unavailable in the language at all.
        [Pure]
        public StringifiedExpression DispatchTypeEqual(Expression e)
            => throw new NotImplementedException();

        [Pure]
        public StringifiedExpression DispatchDebugInfo(Expression e)
            => throw new NotImplementedException();

        [Pure]
        public StringifiedExpression DispatchDynamic(Expression e)
            => throw new NotImplementedException();

        [Pure]
        public StringifiedExpression DispatchDefault(Expression e)
        {
            var defExpr = (DefaultExpression)e;

            return StringifiedExpression.TextOnly("default(" + objectStringifier.TypeNameToCode(defExpr.Type) + ")");
        }

        [Pure]
        public StringifiedExpression DispatchExtension(Expression e)
            => throw new NotImplementedException();

        [Pure]
        public StringifiedExpression DispatchGoto(Expression e)
            => throw new NotImplementedException();

        [Pure]
        public StringifiedExpression DispatchLabel(Expression e)
            => throw new NotImplementedException();

        [Pure]
        public StringifiedExpression DispatchRuntimeVariables(Expression e)
            => throw new NotImplementedException();

        [Pure]
        public StringifiedExpression DispatchLoop(Expression e)
            => throw new NotImplementedException();

        [Pure]
        public StringifiedExpression DispatchSwitch(Expression e)
            => throw new NotImplementedException();

        [Pure]
        public StringifiedExpression DispatchThrow(Expression e)
            => throw new NotImplementedException();

        [Pure]
        public StringifiedExpression DispatchTry(Expression e)
            => throw new NotImplementedException();

        [Pure]
        public StringifiedExpression DispatchUnbox(Expression e)
            => throw new NotImplementedException();

        [Pure]
        public StringifiedExpression DispatchPowerAssign(Expression e)
            => throw new NotImplementedException();

        [Pure]
        public StringifiedExpression DispatchIsTrue(Expression e)
            => throw new NotImplementedException();

        [Pure]
        public StringifiedExpression DispatchIsFalse(Expression e)
            => throw new NotImplementedException();
        #endregion
    }
}


<|start_filename|>ExpressionToCodeLib/IObjectStringifier.cs<|end_filename|>
﻿using System;

namespace ExpressionToCodeLib
{
    public interface IObjectStringifier
    {
        string? PlainObjectToCode(object? val, Type? type);
        string TypeNameToCode(Type type);
    }

    public static class ObjectStringifierExtensions
    {
        public static string? PlainObjectToCode(this IObjectStringifier it, object? val)
            => it.PlainObjectToCode(val, val?.GetType());
    }
}


<|start_filename|>ExpressionToCodeTest/ValueTupleTests.cs<|end_filename|>
﻿using System;
using ExpressionToCodeLib;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using FastExpressionCompiler;
using Xunit;

namespace ExpressionToCodeTest
{
    public struct MyValueTuple<T1, T2> : IEquatable<MyValueTuple<T1, T2>>
    {
        public readonly T1 v1;
        public readonly T2 v2;

        public MyValueTuple((T1 v1, T2 v2) tuple)
            => (v1, v2) = tuple;

        public bool Equals(MyValueTuple<T1, T2> other)
            => Equals(v1, other.v1) && Equals(v2, other.v2);
    }

    public class ValueTupleTests
    {
        static MyValueTuple<T1, T2> ToMyValueTuple<T1, T2>((T1, T2) tuple)
            => new MyValueTuple<T1, T2>(tuple);

        [Fact]
        public void ExpressionWithValueTupleEqualsCanCompile()
        {
            //was a test for https://github.com/dotnet/roslyn/issues/27322
            var tupleA = (1, 3);
            var tupleB = (1, "123".Length);

            Expression<Func<int>> ok1 = () => tupleA.Item1;
            Expression<Func<int>> ok2 = () => tupleA.GetHashCode();
            Expression<Func<Tuple<int, int>>> ok3 = () => tupleA.ToTuple();
            Expression<Func<bool>> ok4 = () => Equals(tupleA, tupleB);
            Expression<Func<int>> ok5 = () => Comparer<(int, int)>.Default.Compare(tupleA, tupleB);
            ok1.Compile()();
            ok2.Compile()();
            ok3.Compile()();
            ok4.Compile()();
            ok5.Compile()();

            var myTupleA = ToMyValueTuple(tupleA);
            var myTupleB = ToMyValueTuple(tupleB);
            Expression<Func<bool>> ok6 = () => myTupleA.Equals(myTupleB);
            Expression<Func<bool>> ok7 = () => tupleA.ToTuple().Equals(tupleB.ToTuple());
            Expression<Func<bool>> ok8 = () => ToMyValueTuple(tupleA).Equals(ToMyValueTuple(tupleB));
            ok6.Compile()();
            ok7.Compile()();
            ok8.Compile()();

            // ReSharper disable once UnusedVariable
            Expression<Func<bool>> err1 = () => tupleA.Equals(tupleB); //crash
            // ReSharper disable once UnusedVariable
            Expression<Func<int>> err2 = () => tupleA.CompareTo(tupleB); //crash
        }

        [Fact]
        public void FastExpressionCompileValueTupleEqualsWorks()
        {
            //was a test for https://github.com/dotnet/roslyn/issues/27322
            var tuple = (1, 3);
            var tuple2 = (1, "123".Length);
            var expr = ExpressionCompiler.CompileFast(() => tuple.Equals(tuple2));
            Assert.True(expr());
        }

        [Fact]
        public void AssertingOnValueTupleEqualsWorks()
        {
            //was a test for https://github.com/dotnet/roslyn/issues/27322
            var tuple = (1, 3);
            var tuple2 = (1, "123".Length);
            Expression<Func<bool>> expr = () => tuple.Equals(tuple2);
            Assert.True(expr.Compile()());
            PAssert.That(() => tuple.Equals(tuple2));
        }

        [Fact]
        public void ToCSharpFriendlyTypeNameSupportsTuples()
        {
            var actual = (1, "2", new[] { 1, 2, 3 });
            Assert.Equal("(int, string, int[])", actual.GetType().ToCSharpFriendlyTypeName());
        }

        [Fact]
        public void ToCSharpFriendlyTypeNameSupportsLooongTuples()
        {
            var actual = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
            Assert.Equal("(int, int, int, int, int, int, int, int, int, int)", actual.GetType().ToCSharpFriendlyTypeName());
        }

        [Fact]
        public void ToCSharpFriendlyTypeNameSupportsNestedTuples()
        {
            var actual = (1, 2, ((3, 4), 5), 6, 7, 8);
            Assert.Equal("(int, int, ((int, int), int), int, int, int)", actual.GetType().ToCSharpFriendlyTypeName());
        }

        [Fact]
        public void ToCSharpFriendlyTypeNameSupportsNestedNullableTuples()
        {
            var actual = default((int, int, ((int, int), int), (int, int)?, int, int));
            Assert.Equal("(int, int, ((int, int), int), (int, int)?, int, int)", actual.GetType().ToCSharpFriendlyTypeName());
        }

        [Fact]
        public void ToCSharpFriendlyTypeNameSupportsTrailingNestedTuples()
        {
            var actual = default((int, int, ((int, int), int)));
            Assert.Equal("(int, int, ((int, int), int))", actual.GetType().ToCSharpFriendlyTypeName());
        }

        [Fact]
        public void ToCSharpFriendlyTypeNameSupportsTrailingNestedTuplesAtPosition8()
        {
            var actual = default((int, int, int, int, int, int, int, (int, int)));
            var alt = default((int, int, int, int, int, int, int, int, int));
            Assert.False(actual.GetType() == alt.GetType()); //non-obvious compiler-guarranteed precondition
            Assert.Equal("(int, int, int, int, int, int, int, (int, int))", actual.GetType().ToCSharpFriendlyTypeName());
        }

        [Fact]
        public void ComplexObjectToPseudoCodeSupportsTuples()
        {
            var actual = (1, "2", new[] { 1, 2, 3 });
            Assert.Equal("(1, \"2\", new[] { 1, 2, 3 })", ObjectToCode.ComplexObjectToPseudoCode(actual));
        }

        [Fact]
        public void ComplexObjectToPseudoCodeSupportsComplexTuples()
        {
            var actual = (1, ("2", new[] { "2b" }), new[] { 3 }, 4, default(string), 6, 7, 8, 9);
            Assert.Equal("(1, (\"2\", new[] { \"2b\" }), new[] { 3 }, 4, null, 6, 7, 8, 9)", ObjectToCode.ComplexObjectToPseudoCode(actual));
        }

        [Fact]
        public void ComplexObjectToPseudoCodeSupportsTuplesWithTrailingTuples()
        {
            var actual = (1, 2, 3, 4, 5, 6, 7, (8, 9, 10));
            Assert.Equal("(1, 2, 3, 4, 5, 6, 7, (8, 9, 10))", ObjectToCode.ComplexObjectToPseudoCode(actual));
        }
    }
}


<|start_filename|>ExpressionToCodeTest/ExpressionInterpretationBug.cs<|end_filename|>
﻿#if expression_interpretation
using System;
using System.Linq.Expressions;
using FastExpressionCompiler;
using Xunit;

namespace ExpressionToCodeTest
{
    /// <summary>
    /// Repro for https://github.com/dotnet/runtime/issues/29673
    /// </summary>
    public class ExpressionInterpretationBug
    {
        struct AStruct
        {
            public int AValue;
        }

        class SomethingMutable
        {
            public AStruct AStructField;
        }

        static Expression<Func<T>> Expr<T>(Expression<Func<T>> e)
            => e;

        [Fact]
        public void MemberMemberBindingForStructsCompilesOk()
        {
            var expr_compiled = Expr(() => new SomethingMutable { AStructField = { AValue = 2 } }).Compile(false)();
            Assert.Equal(2, expr_compiled.AStructField.AValue);
        }
#if !NETFRAMEWORK
        [Fact]
        public void MemberMemberBindingForStructsInterpretsWrong_BUGBUG()
        {
            var expr_interpreted = Expr(() => new SomethingMutable { AStructField = { AValue = 2 } }).Compile(true)();
            //Assert.Equal(2, expr_interpreted.AStructField.AValue);  //this should hold, but instead:
            Assert.Equal(0, expr_interpreted.AStructField.AValue); //this way I might notice when the bug gets fixed
        }
#endif

        [Fact]
        public void MemberMemberBindingForStructsFastCompilesOk()
        {
            var expr_compile_fast = Expr(() => new SomethingMutable { AStructField = { AValue = 2 } }).CompileFast()();
            Assert.Equal(2, expr_compile_fast.AStructField.AValue);
        }
    }
}
#endif


<|start_filename|>ExpressionToCodeTest/ArrayAccessTests.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using ExpressionToCodeLib;
using Xunit;

namespace ExpressionToCodeTest
{
    public class ArrayAccessTests
    {
        [Fact]
        public void TestSingleDimensionalArrayIndexExpressionWithLambda()
        {
            var param = Expression.Parameter(typeof(string[]), "a");
            var expr = Expression.Lambda(
                Expression.ArrayIndex(param, Expression.Constant(1)),
                param
            );
            Assert.Equal(
                "a => a[1]",
                ExpressionToCode.ToCode(expr)
            );
        }

        [Fact]
        public void TestSingleDimensionalArrayAccessExpressionWithLambda()
        {
            var param = Expression.Parameter(typeof(string[]), "a");
            var expr = Expression.Lambda(
                Expression.ArrayAccess(param, Expression.Constant(1)),
                param
            );
            Assert.Equal(
                "a => a[1]",
                ExpressionToCode.ToCode(expr)
            );
        }
    }
}


<|start_filename|>ExpressionToCodeTest/ValueOnStalksAnnotatorTest.cs<|end_filename|>
﻿using System;
using ExpressionToCodeLib;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Xunit;
using System.Linq.Expressions;

namespace ExpressionToCodeTest
{
    public class ValueOnStalksAnnotatorTest
    {
        static readonly ExpressionToCodeConfiguration config =
            ExpressionToCodeConfiguration.DefaultAssertionConfiguration.WithAnnotator(CodeAnnotators.ValuesOnStalksCodeAnnotator);

        static readonly IAnnotatedToCode annotator = config.GetAnnotatedToCode();

        static string AnnotateAsAssertion<T>(Expression<Func<T>> expr) => annotator.AnnotatedToCode(expr.Body, "assertion failed", true);

        [Fact]
        public void AplusBapproved()
        {
            var a = 2;
            var b = 5;
            ApprovalTest.Verify(AnnotateAsAssertion(() => a + b > 3));
        }

        [Fact]
        public void Binary_expressions_with_nesting()
        {
            var a = 2;
            var b = 5;
            var c = 3.45;
            ApprovalTest.Verify(AnnotateAsAssertion(() => a < b && (c > -a || c > b) && b < 10));
        }

        [Fact]
        public void DealsOkWithLongEnumerables()
            => ApprovalTest.Verify(
                AnnotateAsAssertion(
                    () => Enumerable.Range(0, 1000).ToDictionary(i => "n" + i)["n3"].ToString(CultureInfo.InvariantCulture) == 3.5.ToString(CultureInfo.InvariantCulture)
                ));

        [Fact]
        public void DealsOkWithLongStrings()
            => ApprovalTest.Verify(
                AnnotateAsAssertion(
                    () => string.Join("##", Enumerable.Range(0, 100)) + "suffix"
                ));

        [Fact]
        public void DealsOkWithObjectsContainingLongStrings()
            => ApprovalTest.Verify(
                AnnotateAsAssertion(
                    () => new {
                        A_long_string = string.Join("##", Enumerable.Range(0, 100)) + "suffix",
                        A_short_string = "short",
                        A_long_enumerable = Enumerable.Range(0, 1000)
                    }
                ));

        [Fact]
        public void DealsOkWithEnumerablesOfAnonymousObjects()
        {
            var foo = new {
                A_long_string = string.Join("##", Enumerable.Range(0, 100)) + "suffix",
                A_short_string = "short",
                A_long_enumerable = Enumerable.Range(0, 1000)
            };
            ApprovalTest.Verify(
                AnnotateAsAssertion(
                    () => new[] {
                        foo,
                        foo,
                    }));
        }

        [Fact]
        public void DealsOkWithObjectsContainingLongMultilineStrings()
        {
            var wallOfText =
                string.Join(
                    "",
                    Enumerable.Range(0, 100)
                        .Select(
                            line =>
                                $"line {line}:".PadRight(10)
                                + string.Join(
                                    "",
                                    Enumerable.Range(2, 20).Select(n => $"{n * 10,9};")
                                ) + "\n"
                        )
                );

            ApprovalTest.Verify(
                AnnotateAsAssertion(
                    () => new {
                        A_wall_of_text = wallOfText,
                        A_short_string = "short",
                        A_long_enumerable = Enumerable.Range(0, 1000)
                    }
                ));
        }

        [Fact]
        public void MessyStructureElidesNeatly()
        {
            var hmm = "1234567890";
            ApprovalTest.Verify(
                AnnotateAsAssertion(
                    // ReSharper disable once RedundantLogicalConditionalExpressionOperand
                    () => hmm[1] == hmm[2] || hmm[4] == hmm[int.Parse(hmm[8].ToString())] || false
                ));
        }

        [Fact]
        public void MethodCallsAndArrayLiterals()
        {
            var a = 2;
            var b = 5;
            ApprovalTest.Verify(AnnotateAsAssertion(() => Math.Max(a, b) > new[] { 3, 8, 13, 4 }.Average()));
        }

        [Fact]
        public void NestedArrayAccess()
        {
            var a = 2;
            var b = 5;
            var nums = Enumerable.Range(10, 10).ToArray();
            ApprovalTest.Verify(AnnotateAsAssertion(() => nums[a + b] < 7));
        }

        [Fact]
        public void NestedArrayAccessWithOuterAnd()
        {
            var a = 2;
            var b = 5;
            var nums = Enumerable.Range(10, 10).ToArray();
            ApprovalTest.Verify(AnnotateAsAssertion(() => a < b && nums[a + b] < 7 && b < 10));
        }

        [Fact]
        public void NodesThatAreUndescribableAreNotDescribed()
        {
            var list = new List<int> { 1, 2, 3, 3, 2, 1 };
            ApprovalTest.Verify(AnnotateAsAssertion(() => list.Select(e => e + 1).Count() == 5));
        }
    }
}


<|start_filename|>ExpressionToCodeLib/ExpressionToCodeConfiguration.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using ExpressionToCodeLib.Internal;

// ReSharper disable MemberCanBePrivate.Global
namespace ExpressionToCodeLib
{
    struct ExpressionToCodeConfigurationValue
    {
        public ICodeAnnotator CodeAnnotator;
        public IExpressionCompiler ExpressionCompiler;
        public IObjectStringifier ObjectStringifier;
        public bool AlwaysUseExplicitTypeArguments;
        public bool OmitImplicitCasts;
        public int? PrintedListLengthLimit;
        public int? MaximumValueLength;
    }

    /// <summary>
    ///     Specifies details of how expressions and their values are to be formatted.  This object is immutable; all instance
    ///     methods are thread safe.
    ///     Changes to configuration return new configuration instances.
    /// </summary>
    public class ExpressionToCodeConfiguration
    {
        /// <summary>
        ///     The default formatter for converting an expression to code. Defaults are:
        ///     <para>- Avoid generic type parameters in output where they can be inferred.</para>
        ///     <para>- Omit namespaces from type names (as opposed to fully qualifying type names)</para>
        ///     <para>- Use the default .net expression compiler (as opposed to the experimental optimized compiler).</para>
        ///     <para>- Annotate values using "stalks" hanging under expressions.</para>
        ///     <para>- Print all elements in an enumerable (this will cause crashes on infinite or very large enumerables).</para>
        /// </summary>
        public static readonly ExpressionToCodeConfiguration DefaultCodeGenConfiguration =
            new ExpressionToCodeConfiguration(
                new ExpressionToCodeConfigurationValue {
                    CodeAnnotator = CodeAnnotators.SubExpressionPerLineCodeAnnotator,
                    ExpressionCompiler = ExpressionTreeCompilers.DotnetExpressionCompiler,
                    ObjectStringifier = ObjectStringify.Default,
                    AlwaysUseExplicitTypeArguments = false,
                    OmitImplicitCasts = true,
                });

        /// <summary>
        ///     The default formatter for formatting an assertion violation.
        ///     This is identical to DefaultCodeGenConfiguration, except that enumerable contents after the first 10 elements are
        ///     elided.
        /// </summary>
        public static readonly ExpressionToCodeConfiguration DefaultAssertionConfiguration =
            DefaultCodeGenConfiguration
                .WithPrintedListLengthLimit(30)
                .WithMaximumValueLength(150)
                .WithOmitImplicitCasts(true);

        /// <summary>
        ///     This configuration is used for PAssert.That(()=>...) and Expect(()=>...).  Initially
        ///     ExpressionToCodeConfiguration.DefaultAssertionConfiguration.
        ///     <para>
        ///         This field is globally mutable to allow consumers to configure the library.  If you wish to use multiple
        ///         configurations, it is recommended
        ///         to use the instance methods on a configuration instance instead of the static methods in PAssert,
        ///         ExpressionToCode and ExpressionAssertions.
        ///     </para>
        /// </summary>
        // ReSharper disable once FieldCanBeMadeReadOnly.Global
        public static ExpressionToCodeConfiguration GlobalAssertionConfiguration = DefaultAssertionConfiguration;

        /// <summary>
        ///     This configuration is used for Expression.ToCode(() => ...) and other code-generation methods. Initially
        ///     ExpressionToCodeConfiguration.DefaultCodeGenConfiguration.
        ///     <para>
        ///         This field is globally mutable to allow consumers to configure the library.  If you wish to use multiple
        ///         configurations, it is recommended
        ///         to use the instance methods on a configuration instance instead of the static methods in PAssert,
        ///         ExpressionToCode and ExpressionAssertions.
        ///     </para>
        /// </summary>
        // ReSharper disable once FieldCanBeMadeReadOnly.Global
        public static ExpressionToCodeConfiguration GlobalCodeGenConfiguration = DefaultCodeGenConfiguration;

        internal readonly ExpressionToCodeConfigurationValue Value;

        ExpressionToCodeConfiguration(ExpressionToCodeConfigurationValue value)
            => Value = value;

        delegate void WithDelegate(ref ExpressionToCodeConfigurationValue configToEdit);

        ExpressionToCodeConfiguration With(WithDelegate edit)
        {
            var configCopy = Value;
            edit(ref configCopy);
            return new ExpressionToCodeConfiguration(configCopy);
        }

        public ExpressionToCodeConfiguration WithCompiler(IExpressionCompiler compiler)
            => With((ref ExpressionToCodeConfigurationValue a) => a.ExpressionCompiler = compiler);

        public ExpressionToCodeConfiguration WithAnnotator(ICodeAnnotator annotator)
            => With((ref ExpressionToCodeConfigurationValue a) => a.CodeAnnotator = annotator);

        public ExpressionToCodeConfiguration WithPrintedListLengthLimit(int? limitListsToLength)
            => With((ref ExpressionToCodeConfigurationValue a) => a.PrintedListLengthLimit = limitListsToLength);

        public ExpressionToCodeConfiguration WithMaximumValueLength(int? limitValueStringsToLength)
            => With((ref ExpressionToCodeConfigurationValue a) => a.MaximumValueLength = limitValueStringsToLength);

        /// <summary>
        ///     Omits builtin implicit casts.  This can cause the code to select another overload, so it's off by default for the
        ///     code-gen config.
        /// </summary>
        public ExpressionToCodeConfiguration WithOmitImplicitCasts(bool omitImplicitCasts)
            => With((ref ExpressionToCodeConfigurationValue a) => a.OmitImplicitCasts = omitImplicitCasts);

        public ExpressionToCodeConfiguration WithObjectStringifier(IObjectStringifier objectStringifier)
            => With((ref ExpressionToCodeConfigurationValue a) => a.ObjectStringifier = objectStringifier);

        public ExpressionToCodeConfiguration WithAlwaysUseExplicitTypeArguments(bool alwaysUseExplicitTypeArguments)
            => With((ref ExpressionToCodeConfigurationValue a) => a.AlwaysUseExplicitTypeArguments = alwaysUseExplicitTypeArguments);

        public IExpressionToCode GetExpressionToCode()
            => new ExpressionToCodeWrapper(this);

        public IAnnotatedToCode GetAnnotatedToCode()
            => new AnnotatedToCodeWrapper(this);

        sealed class AnnotatedToCodeWrapper : IAnnotatedToCode
        {
            readonly ExpressionToCodeConfiguration config;

            public AnnotatedToCodeWrapper(ExpressionToCodeConfiguration config)
                => this.config = config;

            public string AnnotatedToCode(Expression e, string? msg, bool outerValueIsAssertionFailure)
                => config.Value.CodeAnnotator.AnnotateExpressionTree(config, e, msg, outerValueIsAssertionFailure);
        }

        sealed class ExpressionToCodeWrapper : IExpressionToCode
        {
            readonly ExpressionToCodeConfiguration config;

            public ExpressionToCodeWrapper(ExpressionToCodeConfiguration config)
                => this.config = config;

            public string ToCode(Expression e)
                => ExpressionToCodeString.ToCodeString(config, e);
        }
    }

    public interface ICodeAnnotator
    {
        string AnnotateExpressionTree(ExpressionToCodeConfiguration config, Expression expr, string? msg, bool outerValueIsAssertionFailure);
    }

    public interface IExpressionCompiler
    {
        Func<T> Compile<T>(Expression<Func<T>> expression);
        Delegate Compile(LambdaExpression expression);
    }
}


<|start_filename|>ExpressionToCodeLib/Internal/DotnetExpressionCompiler.cs<|end_filename|>
﻿using System;
using System.Linq.Expressions;

namespace ExpressionToCodeLib.Internal
{
    sealed class DotnetExpressionCompiler : IExpressionCompiler
    {
        public Func<T> Compile<T>(Expression<Func<T>> expression)
            => expression.Compile(true);

        public Delegate Compile(LambdaExpression expression)
            => expression.Compile(true);
    }
}


<|start_filename|>ExpressionToCodeTest/NestedClassTest.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using ExpressionToCodeLib;
using Xunit;

namespace ExpressionToCodeTest
{
    public class Parent
    {
        public class Nested { }

        // ReSharper disable once UnusedTypeParameter
        public class NestedGen<T> { }
    }

    // ReSharper disable once UnusedTypeParameter
    public class ParentGen<T>
    {
        public class Nested { }

        // ReSharper disable once UnusedTypeParameter
        public class NestedGen<T2> { }
    }

    public class NestedClassTest
    {
        [Fact]
        public void PlainNested()
            => Assert.Equal(
                "() => null as Parent.Nested",
                ExpressionToCode.ToCode(() => null as Parent.Nested));

        [Fact]
        public void GenericNested()
        {
            Assert.Equal("() => null as Parent.NestedGen<int>", ExpressionToCode.ToCode(() => null as Parent.NestedGen<int>));
            Assert.Equal("() => null as Parent.NestedGen<Parent.NestedGen<object>>", ExpressionToCode.ToCode(() => null as Parent.NestedGen<Parent.NestedGen<object>>));
        }

        [Fact]
        public void NestedInGeneric()
        {
            Assert.Equal("() => null as ParentGen<int>.Nested", ExpressionToCode.ToCode(() => null as ParentGen<int>.Nested));
            Assert.Equal("() => null as ParentGen<ParentGen<string>.Nested>.Nested", ExpressionToCode.ToCode(() => null as ParentGen<ParentGen<string>.Nested>.Nested));
        }

        [Fact]
        public void GenericNestedInGeneric()
        {
            Assert.Equal("() => null as ParentGen<int>.NestedGen<string>", ExpressionToCode.ToCode(() => null as ParentGen<int>.NestedGen<string>));
            Assert.Equal(
                "() => null as ParentGen<Parent.NestedGen<object>>.NestedGen<string>",
                ExpressionToCode.ToCode(() => null as ParentGen<Parent.NestedGen<object>>.NestedGen<string>));
            Assert.Equal(
                "() => null as ParentGen<int>.NestedGen<ParentGen<int>.Nested>",
                ExpressionToCode.ToCode(() => null as ParentGen<int>.NestedGen<ParentGen<int>.Nested>));
            Assert.Equal(
                "() => null as ParentGen<ParentGen<int>.Nested>.NestedGen<ParentGen<int>.NestedGen<string>>",
                ExpressionToCode.ToCode(() => null as ParentGen<ParentGen<int>.Nested>.NestedGen<ParentGen<int>.NestedGen<string>>));
        }
    }
}


<|start_filename|>ExpressionToCodeTest/GenericsTestClasses.cs<|end_filename|>
﻿using System.Reflection;
using Xunit;
using Assert = Xunit.Assert;
// ReSharper disable ConvertToConstant.Local
// ReSharper disable RedundantEnumerableCastCall
// ReSharper disable MemberCanBeMadeStatic.Local
using System;
using System.Collections.Generic;
using System.Linq;
using ExpressionToCodeLib;
// ReSharper disable UnusedParameter.Global

namespace ExpressionToCodeTest
{
    public sealed class TestGenerics
    {
        [Fact]
        public void TypeParameters()
        {
            Assert.Equal(1337, StaticTestClass.Consume(12));
            Assert.Equal(42, StaticTestClass.Consume<int>(12));
            Assert.Equal(42, StaticTestClass.Consume('a'));
            Assert.Equal(42, StaticTestClass.IndirectConsume(12));

            Assert.Equal(
                @"() => 1337 == StaticTestClass.Consume(12)",
                ExpressionToCode.ToCode(() => 1337 == StaticTestClass.Consume(12))
            );
            Assert.Equal(
                @"() => 42 == StaticTestClass.Consume('a')",
                ExpressionToCode.ToCode(() => 42 == StaticTestClass.Consume('a'))
            );
            Assert.Equal(
                @"() => 42 == StaticTestClass.IndirectConsume(12)",
                ExpressionToCode.ToCode(() => 42 == StaticTestClass.IndirectConsume(12))
            );
            Assert.Equal(
                @"() => 42 == StaticTestClass.Consume<int>(12)",
                ExpressionToCode.ToCode(() => 42 == StaticTestClass.Consume<int>(12))
            ); //should not remove type parameters where this would cause ambiguity due to overloads!
        }

        [Fact]
        public void TypeParameters2()
        {
            Assert.Equal(
                @"() => new[] { 1, 2, 3 }.First()",
                ExpressionToCode.ToCode(() => new[] { 1, 2, 3 }.First())
            ); //should remove type parameters where they can be inferred.

            Assert.Equal(
                @"() => new[] { 1, 2, 3 }.Select(x => x.ToString())",
                ExpressionToCode.ToCode(() => new[] { 1, 2, 3 }.Select(x => x.ToString()))
            ); //should remove type parameters where they can be inferred.
        }

        [Fact]
        public void TypeParameters3()
            => Assert.Equal(
                @"() => new[] { 1, 2, 3 }.Cast<int>()",
                ExpressionToCode.ToCode(() => new[] { 1, 2, 3 }.Cast<int>())
            );

        [Fact]
        public void GenericConstructor()
        {
            Assert.Equal(
                @"() => new List<int>()",
                ExpressionToCode.ToCode(() => new List<int>())
            );
            Assert.Equal(
                @"() => new GenericClass<int>(3)",
                ExpressionToCode.ToCode(() => new GenericClass<int>(3))
            );
            Assert.Equal(
                @"() => new GenericSubClass<IEnumerable<int>, int>(new[] { 3 })",
                ExpressionToCode.ToCode(() => new GenericSubClass<IEnumerable<int>, int>(new[] { 3 }))
            );
        }

        [Fact]
        public void MethodInGenericClass()
        {
            Assert.Equal(
                @"() => new GenericClass<int>(3).IsSet(3)",
                ExpressionToCode.ToCode(() => new GenericClass<int>(3).IsSet(3))
            );
            Assert.Equal(
                @"() => GenericClass<int>.GetDefault()",
                ExpressionToCode.ToCode(() => GenericClass<int>.GetDefault())
            );
        }

        [Fact]
        public void GenericMethodInGenericClass()
        {
            var x = new GenericClass<string>("42");
            var y = new GenericClass<object>("42");
            Assert.True(x.IsSubEqual("42"));
            Assert.True(x.IsSubClass<string>());
            Assert.True(y.IsSubClass<string>());
            Assert.Equal(
                @"() => x.IsSubEqual(""42"")",
                ExpressionToCode.ToCode(() => x.IsSubEqual("42"))
            );
            Assert.Equal(
                @"() => x.IsSubClass<string>()",
                ExpressionToCode.ToCode(() => x.IsSubClass<string>())
            );
            Assert.Equal(
                @"() => y.IsSubClass<string>()",
                ExpressionToCode.ToCode(() => y.IsSubClass<string>())
            );
        }

        [Fact]
        public void StraightforwardInference()
            => Assert.Equal(
                @"() => StaticTestClass.Identity(3)",
                ExpressionToCode.ToCode(() => StaticTestClass.Identity(3))
            );

        [Fact]
        public void CannotInferOneParam()
            => Assert.Equal(
                @"() => StaticTestClass.IsType<int, int>(3)",
                ExpressionToCode.ToCode(() => StaticTestClass.IsType<int, int>(3))
            );

        [Fact]
        public void CannotInferWithoutTParam()
        {
            Assert.Equal(
                @"() => StaticTestClass.TEqualsInt<int>(3)",
                ExpressionToCode.ToCode(() => StaticTestClass.TEqualsInt<int>(3))
            );
            Assert.Equal(
                @"() => StaticTestClass.TEqualsInt<string>(3)",
                ExpressionToCode.ToCode(() => StaticTestClass.TEqualsInt<string>(3))
            );
        }

        [Fact]
        public void CanInferDirect()
        {
            Assert.Equal(
                @"() => StaticTestClass.TwoArgsOneGeneric(3, 3)",
                ExpressionToCode.ToCode(() => StaticTestClass.TwoArgsOneGeneric(3, 3))
            );
            Assert.Equal(
                @"() => StaticTestClass.TwoArgsOneGeneric(3, ""3"")",
                ExpressionToCode.ToCode(() => StaticTestClass.TwoArgsOneGeneric(3, "3"))
            );
        }

        [Fact]
        public void CanInferTwoArg()
        {
            Assert.Equal(
                @"() => StaticTestClass.TwoArgsTwoGeneric(3, 3)",
                ExpressionToCode.ToCode(() => StaticTestClass.TwoArgsTwoGeneric(3, 3))
            );

            Assert.Equal(
                @"() => StaticTestClass.TwoArgsTwoGeneric(3, new object())",
                ExpressionToCode.ToCode(() => StaticTestClass.TwoArgsTwoGeneric(3, new object()))
            );

            var x = 37;
            var y = 42.0;
            Assert.Equal(
                @"() => StaticTestClass.TwoArgsTwoGeneric(x, y)",
                ExpressionToCode.ToCode(() => StaticTestClass.TwoArgsTwoGeneric(x, y))
            );
        }

        [Fact]
        public void CanInferIndirect()
        {
            Assert.True(GenericClass<int>.IsEnumerableOfType(new[] { 3, 4 }));
            Assert.True(GenericClass<int>.IsFuncOfType(() => 3));
            Assert.True(!GenericClass<int>.IsFuncOfType(() => 3.0));
            Assert.True(GenericClass<int>.IsFunc2OfType((int x) => x));

            Assert.Equal(
                @"() => GenericClass<int>.IsEnumerableOfType(new[] { 3, 4 })",
                ExpressionToCode.ToCode(() => GenericClass<int>.IsEnumerableOfType(new[] { 3, 4 }))
            );
            Assert.Equal(
                @"() => GenericClass<int>.IsFuncOfType(() => 3)",
                ExpressionToCode.ToCode(() => GenericClass<int>.IsFuncOfType(() => 3))
            );
            Assert.Equal(
                @"() => !GenericClass<int>.IsFuncOfType(() => 3.0)",
                ExpressionToCode.ToCode(() => !GenericClass<int>.IsFuncOfType(() => 3.0))
            );
            Assert.Equal(
                @"() => GenericClass<int>.IsFunc2OfType<int>(x => x)",
                ExpressionToCode.ToCode(() => GenericClass<int>.IsFunc2OfType<int>(x => x))
            );
        }

        [Fact]
        public void GenericMethodCall_WhenSomeNotInferredTypeArguments_ShouldExplicitlySpecifyTypeArguments()
            => Assert.Equal(
                @"() => StaticTestClass.IsType<int, int>(3)",
                ExpressionToCodeConfiguration.DefaultCodeGenConfiguration.WithAlwaysUseExplicitTypeArguments(true)
                    .WithObjectStringifier(ObjectStringify.Default)
                    .GetExpressionToCode()
                    .ToCode(() => StaticTestClass.IsType<int, int>(3))
            );

        [Fact]
        public void GenericMethodCall_ShouldExplicitlySpecifyTypeArguments()
            => Assert.Equal(
                "() => MakeMe<Cake, string>(() => new Cake())",
                ExpressionToCode.ToCode(() => MakeMe<Cake, string>(() => new Cake())));

        // ReSharper disable once UnusedTypeParameter
        T MakeMe<T, TNotInferredFromArgument>(Func<T> maker)
            => maker();

        [Fact]
        public void UsesBoundTypeNamesEvenInGenericMethod()
            => AssertInGenericMethodWithIntArg<int>();

        void AssertInGenericMethodWithIntArg<T>()
            => Assert.Equal(
                "() => new List<int>()",
                ExpressionToCode.ToCode(() => new List<T>()));
    }

    // ReSharper disable MemberCanBeProtected.Global
    sealed class Cake { }

    class GenericClass<T>
    {
        T val;

        public GenericClass(T pVal)
            => val = pVal;

        public T Value
            => val;

        // ReSharper disable once UnusedMember.Global
        public void Reset(T pVal)
            => val = pVal;

        public bool IsSet(T pVal)
            => Equals(pVal, val);

        public static T GetDefault()
            => default!;

        public static bool IsEnumerableOfType<U>(IEnumerable<U> x)
            => typeof(T).GetTypeInfo().IsAssignableFrom(typeof(U));

        public static bool IsFuncOfType<U>(Func<U> x)
            => typeof(T).IsAssignableFrom(typeof(U));

        public static bool IsFunc2OfType<U>(Func<U, U> x)
            => typeof(T).IsAssignableFrom(typeof(U));

        public bool IsSubClass<U>()
            where U : T
            => val is U;

        public bool IsSubEqual<U>(U other)
            where U : T, IEquatable<T>
            => other.Equals(val);
    }

    sealed class GenericSubClass<T, U> : GenericClass<T>
        where T : IEnumerable<U>
    {
        public GenericSubClass(T val)
            : base(val) { }

        // ReSharper disable once UnusedMember.Global
        public bool IsEmpty
            => !Value.Any();
    }

    public static class StaticTestClass
    {
        public static T Identity<T>(T val)
            => val;

        public static bool IsType<T, U>(T val)
            => val is U;

        public static bool TEqualsInt<T>(int val)
            => val is T;

        public static bool TwoArgsOneGeneric<T>(int val, T other)
            => val.Equals(other);

        public static bool TwoArgsTwoGeneric<T>(T val, T other)
            => val!.Equals(other);

        public static int Consume<T>(T val)
            => 42;

        public static int Consume(int val)
            => 1337;

        public static int IndirectConsume<T>(T val)
            => Consume(val);
    }
}


<|start_filename|>ExpressionToCodeLib/FormatStringParser.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Text;

namespace ExpressionToCodeLib
{
    public static class FormatStringParser
    {
        public struct Segment
        {
            public string InitialStringPart;
            public object FollowedByValue;
            public string WithFormatString;
        }

#if !NET452
        public static (Segment[] segments, string Tail) ParseFormatString(FormattableString formattableString)
            => new FormattableStringParser(formattableString.Format, formattableString.GetArguments()).Finish();
#endif

        public static (Segment[] segments, string Tail) ParseFormatString(string formatString, object[] formatArguments)
            => new FormattableStringParser(formatString, formatArguments).Finish();

        sealed class FormattableStringParser : IFormatProvider, ICustomFormatter
        {
            //Format strings have exceptions for stuff like double curly braces
            //There are also corner-cases that non-compiler generated format strings might hit
            //All in all: rather than reimplement a custom parser, this
            readonly StringBuilder sb = new StringBuilder();
            readonly List<Segment> Segments = new List<Segment>();

            public FormattableStringParser(string formatString, object[] formatArguments)
                => sb.AppendFormat(this, formatString, formatArguments);

            object IFormatProvider.GetFormat(Type formatType)
                => this;

            public (Segment[] segments, string Tail) Finish()
                => (Segments.ToArray(), sb.ToString());

            string ICustomFormatter.Format(string format, object arg, IFormatProvider formatProvider)
            {
                Segments.Add(
                    new Segment {
                        InitialStringPart = sb.ToString(),
                        FollowedByValue = arg,
                        WithFormatString = format,
                    });
                sb.Length = 0;
                return "";
            }
        }
    }
}


<|start_filename|>ExpressionToCodeTest/JunkClassesForTesting.cs<|end_filename|>
﻿using System;
using JetBrains.Annotations;

namespace ExpressionToCodeTest
{
    // ReSharper disable UnusedTypeParameter
    // ReSharper disable ClassNeverInstantiated.Global
    public class Outer<X, Y>
    {
        public class Nested<Z>
        {
            [UsedImplicitly]
            public void Method(Func<Z> arg) { }
        }

        public class Nested2 { }
    }

    public class Outer2
    {
        public class Nested3<Z>
        {
            [UsedImplicitly]
            public void Method(Func<Z> arg) { }
        }
    }
}


<|start_filename|>ExpressionToCodeLib/Internal/StringifiedExpression.cs<|end_filename|>
﻿using System;
using System.Diagnostics.Contracts;
using System.Linq.Expressions;

namespace ExpressionToCodeLib.Internal
{
    struct StringifiedExpression
    {
        //a node cannot have children and text.  If it has neither, it is considered empty.
        public readonly string? Text;
        readonly StringifiedExpression[]? children;

        //can only have a value it it has text.
        public readonly Expression? OptionalValue;

        /// <summary>
        ///     The expression tree contains many symbols that are not themselves "real" expressions, e.g. the "." in "obj.field".
        ///     This field is true for parts that aren't just implemenation details, but proper sub-expressions; e.g. the "x" in "x
        ///     &amp;&amp; y"
        /// </summary>
        public readonly bool IsConceptualChild;

        static readonly StringifiedExpression[] empty = { };
        public StringifiedExpression[] Children => children ?? empty;

        StringifiedExpression(string? text, StringifiedExpression[]? children, Expression? optionalValue, bool isConceptualChild)
        {
            Text = text;
            this.children = children;
            OptionalValue = optionalValue;
            IsConceptualChild = isConceptualChild;
        }

        [Pure]
        public static StringifiedExpression TextOnly(string? text)
            => new StringifiedExpression(text, null, null, false);

        [Pure]
        public static StringifiedExpression TextAndExpr(string text, Expression expr)
        {
            if (expr == null) {
                throw new ArgumentNullException(nameof(expr));
            }

            return new StringifiedExpression(text, null, expr, false);
        }

        [Pure]
        public static StringifiedExpression WithChildren(StringifiedExpression[] children)
            => new StringifiedExpression(null, children, null, false);

        [Pure]
        public override string ToString()
            => Text ?? string.Join("", Children);

        public StringifiedExpression MarkAsConceptualChild()
            => new StringifiedExpression(Text, children, OptionalValue, true);
    }
}


<|start_filename|>ExpressionToCodeLib/ObjectToCode.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using ExpressionToCodeLib.Internal;

namespace ExpressionToCodeLib
{
    /// <summary>
    ///     If you wish to override some formatting aspects of these methods, set
    ///     ExpressionToCodeConfiguration.GlobalCodeGetConfiguration.
    /// </summary>
    public static class ObjectToCode
    {
        public static string ComplexObjectToPseudoCode(object? val)
            => ObjectToCodeImpl.ComplexObjectToPseudoCode(ExpressionToCodeConfiguration.GlobalCodeGenConfiguration, val, 0);

        public static string ComplexObjectToPseudoCode(this ExpressionToCodeConfiguration config, object? val)
            => ObjectToCodeImpl.ComplexObjectToPseudoCode(config, val, 0);

        public static string? PlainObjectToCode(object? val)
            => ExpressionToCodeConfiguration.GlobalCodeGenConfiguration.Value.ObjectStringifier.PlainObjectToCode(val, val?.GetType());

        public static string? PlainObjectToCode(object? val, Type? type)
            => ExpressionToCodeConfiguration.GlobalCodeGenConfiguration.Value.ObjectStringifier.PlainObjectToCode(val, type);

        public static string ToCSharpFriendlyTypeName(this Type type)
            => new CSharpFriendlyTypeName { IncludeGenericTypeArgumentNames = true }.GetTypeName(type);
    }
}


<|start_filename|>ExpressionToCodeTest/ApprovalTest.cs<|end_filename|>
﻿using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using Assent;
using Assent.Namers;

namespace ExpressionToCodeTest
{
    static class ApprovalTest
    {
        public static void Verify(string text, [CallerFilePath] string? filepath = null, [CallerMemberName] string? membername = null)
        {
            var filename = Path.GetFileNameWithoutExtension(filepath);
            var filedir = Path.GetDirectoryName(filepath) ?? throw new InvalidOperationException("path " + filepath + " has no directory");
            var baseName = Path.Combine(filedir, filename + "." + membername);
            var config = new Configuration().UsingNamer(new FixedNamer(baseName))
                .UsingReporter(
                    (received, approved) => {
                        var newText = File.ReadAllText(received, Encoding.UTF8);
                        var oldText = File.ReadAllText(approved, Encoding.UTF8);
                        File.WriteAllText(approved, newText, Encoding.UTF8);
                        if (newText != oldText) {
                            throw new Exception("difference detected!");
                        }
                    });
            // ReSharper disable once ExplicitCallerInfoArgument
            "bla".Assent(text, config, membername, filepath);
            //var writer = WriterFactory.CreateTextWriter(text);
            //var namer = new SaneNamer { Name = filename + "." + membername, SourcePath = filedir };
            //var reporter = new DiffReporter();
            //Approver.Verify(new FileApprover(writer, namer, true), reporter);
        }

        //public class SaneNamer : IApprovalNamer
        //{
        //    public string SourcePath { get; set; }
        //    public string Name { get; set; }
        //}
    }
}
