// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.IO;
using System.Runtime.CompilerServices;
using Xunit;

namespace Microsoft.CSharp.RuntimeBinder.Tests
{
    [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "It is asserting for exception messages and they vary in between .NET Core and NETFX")]
    public class NamedArgumentTests
    {
        public class TypeWithMethods
        {
            public int DoStuff(int x, int y) => x + y;

            public long DoStuff(long z, long a) => z * a;

            public int DoStuff(string s, int i) => i;

            public int DoOtherStuff(int x, int y, int z) => x * y + z;
        }

        [Fact]
        public void OnlyNameFirstArgument()
        {
            CallSite<Func<CallSite, object, object, object, object>> callsite =
                CallSite<Func<CallSite, object, object, object, object>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.None, nameof(TypeWithMethods.DoStuff), Type.EmptyTypes, GetType(),
                        new[]
                        {
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "x"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                        }));
            Func<CallSite, object, object, object, object> target = callsite.Target;
            object res = target(callsite, new TypeWithMethods(), 9, 14);
            Assert.Equal(23, res);
        }

        [Fact]
        public void OnlyNameFirstArgumentMatchesWrongType()
        {
            CallSite<Func<CallSite, object, object, object, object>> callsite =
                CallSite<Func<CallSite, object, object, object, object>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.None, nameof(TypeWithMethods.DoStuff), Type.EmptyTypes, GetType(),
                        new[]
                        {
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "s"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                        }));
            Func<CallSite, object, object, object, object> target = callsite.Target;
            string message = Assert.Throws<RuntimeBinderException>(() => target(callsite, new TypeWithMethods(), 9, 14))
                .Message;

            // All but the (string, int) method should have been excluded from consideration, leaving the binder to
            // complain about it not matching types.
            Assert.Contains(
                "'Microsoft.CSharp.RuntimeBinder.Tests.NamedArgumentTests.TypeWithMethods.DoStuff(string, int)'",
                message);
        }

        [Fact]
        public void ResolveThroughNameOnlyFirstArgument()
        {
            CallSite<Func<CallSite, object, object, object, object>> callsite =
                CallSite<Func<CallSite, object, object, object, object>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.None, nameof(TypeWithMethods.DoStuff), Type.EmptyTypes, GetType(),
                        new[]
                        {
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "z"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                        }));
            Func<CallSite, object, object, object, object> target = callsite.Target;
            object res = target(callsite, new TypeWithMethods(), 9, 14);
            Assert.Equal(126L, res);
        }

        [Fact]
        public void NonExistentNameOnlyFirstArgument()
        {
            CallSite<Func<CallSite, object, object, object, object>> callsite =
                CallSite<Func<CallSite, object, object, object, object>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.None, nameof(TypeWithMethods.DoStuff), Type.EmptyTypes, GetType(),
                        new[]
                        {
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "nada"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                        }));
            Func<CallSite, object, object, object, object> target = callsite.Target;
            string message = Assert.Throws<RuntimeBinderException>(() => target(callsite, new TypeWithMethods(), 9, 14))
                .Message;

            //  The best overload for 'DoStuff' does not have a parameter named 'nada'
            Assert.Contains("'DoStuff'", message);
            Assert.Contains("'nada'", message);
        }

        [Fact]
        public void NameOnlyFirstArgumentWrongPlace()
        {
            CallSite<Func<CallSite, object, object, object, object>> callsite =
                CallSite<Func<CallSite, object, object, object, object>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.None, nameof(TypeWithMethods.DoStuff), Type.EmptyTypes, GetType(),
                        new[]
                        {
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "y"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                        }));
            Func<CallSite, object, object, object, object> target = callsite.Target;
            string message = Assert.Throws<RuntimeBinderException>(() => target(callsite, new TypeWithMethods(), 9, 14))
                .Message;

            //  Named argument 'y' is used out-of-position but is followed by an unnamed argument
            Assert.Contains("'y'", message);
        }

        [Fact]
        public void NameOnlyFirstAndSecondWithSecondArgumentWrongPlace()
        {
            CallSite<Func<CallSite, object, object, object, object, object>> callsite =
                CallSite<Func<CallSite, object, object, object, object, object>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.None, nameof(TypeWithMethods.DoOtherStuff), Type.EmptyTypes, GetType(),
                        new[]
                        {
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "x"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "z"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                        }));
            Func<CallSite, object, object, object, object, object> target = callsite.Target;
            string message = Assert
                .Throws<RuntimeBinderException>(() => target(callsite, new TypeWithMethods(), 9, 14, 13))
                .Message;

            //  Named argument 'z' is used out-of-position but is followed by an unnamed argument
            Assert.Contains("'z'", message);
        }

        [Fact]
        public void NameOnlyFirstAndSecondWithSecondArgumentNotFound()
        {
            CallSite<Func<CallSite, object, object, object, object, object>> callsite =
                CallSite<Func<CallSite, object, object, object, object, object>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.None, nameof(TypeWithMethods.DoOtherStuff), Type.EmptyTypes, GetType(),
                        new[]
                        {
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "x"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "nada"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                        }));
            Func<CallSite, object, object, object, object, object> target = callsite.Target;
            string message = Assert
                .Throws<RuntimeBinderException>(() => target(callsite, new TypeWithMethods(), 9, 14, 12))
                .Message;

            //  The best overload for 'DoMoreStuff' does not have a parameter named 'nada'
            Assert.Contains("'DoOtherStuff'", message);
            Assert.Contains("'nada'", message);
        }

        [Fact]
        public void NameOnlySecondWithSecondArgumentWrongPlace()
        {
            CallSite<Func<CallSite, object, object, object, object, object>> callsite =
                CallSite<Func<CallSite, object, object, object, object, object>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.None, nameof(TypeWithMethods.DoOtherStuff), Type.EmptyTypes, GetType(),
                        new[]
                        {
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "z"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                        }));
            Func<CallSite, object, object, object, object, object> target = callsite.Target;
            string message = Assert
                .Throws<RuntimeBinderException>(() => target(callsite, new TypeWithMethods(), 9, 14, 13))
                .Message;

            //  Named argument 'z' is used out-of-position but is followed by an unnamed argument
            Assert.Contains("'z'", message);
        }

        [Fact]
        public void NameOnlySecondWithSecondArgumentNotFound()
        {
            CallSite<Func<CallSite, object, object, object, object, object>> callsite =
                CallSite<Func<CallSite, object, object, object, object, object>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.None, nameof(TypeWithMethods.DoOtherStuff), Type.EmptyTypes, GetType(),
                        new[]
                        {
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "nada"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                        }));
            Func<CallSite, object, object, object, object, object> target = callsite.Target;
            string message = Assert
                .Throws<RuntimeBinderException>(() => target(callsite, new TypeWithMethods(), 9, 14, 12))
                .Message;

            //  The best overload for 'DoMoreStuff' does not have a parameter named 'nada'
            Assert.Contains("'DoOtherStuff'", message);
            Assert.Contains("'nada'", message);
        }

        class BaseClass
        {
            public virtual int Adder(int x, int y) => x + y;
        }

        class Derived : BaseClass
        {
            public override int Adder(int a, int b) => base.Adder(a, b);
        }

        [Fact]
        public void OverrideChangesName()
        {
            // Static compilation behavior is to use the name of the type of the variable the object is accessed through.
            // Dynamic behavior matches that when the argument is UseCompileTimeType
            // Otherwise it depends on the actual type of the object.

            // Defined in terms of base class. Using "x"
            CallSite<Func<CallSite, BaseClass, int, int, object>> callSite =
                CallSite<Func<CallSite, BaseClass, int, int, object>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.None, nameof(BaseClass.Adder), Type.EmptyTypes, GetType(),
                        new[]
                        {
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                "x"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                        }));
            Assert.Equal(9, callSite.Target(callSite, new Derived(), 4, 5));
            Assert.Equal(9, callSite.Target(callSite, new BaseClass(), 4, 5));

            // Defined in terms of base class. Using "a"
            callSite = CallSite<Func<CallSite, BaseClass, int, int, object>>.Create(
                Binder.InvokeMember(
                    CSharpBinderFlags.None, nameof(BaseClass.Adder), Type.EmptyTypes, GetType(),
                    new[]
                    {
                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                        CSharpArgumentInfo.Create(
                            CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType, "a"),
                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                    }));
            string message = Assert.Throws<RuntimeBinderException>(() => callSite.Target(callSite, new Derived(), 4, 5))
                .Message;

            //  The best overload for 'Adder' does not have a parameter named 'a'
            Assert.Contains("'Adder'", message);
            Assert.Contains("'a'", message);
            Assert.Equal(
                message,
                Assert.Throws<RuntimeBinderException>(() => callSite.Target(callSite, new BaseClass(), 4, 5)).Message);

            // Defined in terms of Derived, using "a"
            CallSite<Func<CallSite, Derived, int, int, object>> callSiteDerived =
                CallSite<Func<CallSite, Derived, int, int, object>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.None, nameof(BaseClass.Adder), Type.EmptyTypes, GetType(),
                        new[]
                        {
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                "a"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                        }));
            Assert.Equal(9, callSiteDerived.Target(callSiteDerived, new Derived(), 4, 5));

            // Defined in terms of Derived, using "x"
            callSiteDerived = CallSite<Func<CallSite, Derived, int, int, object>>.Create(
                Binder.InvokeMember(
                    CSharpBinderFlags.None, nameof(BaseClass.Adder), Type.EmptyTypes, GetType(),
                    new[]
                    {
                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                        CSharpArgumentInfo.Create(
                            CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType, "x"),
                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                    }));
            message = Assert
                .Throws<RuntimeBinderException>(() => callSiteDerived.Target(callSiteDerived, new Derived(), 4, 5))
                .Message;

            //  The best overload for 'Adder' does not have a parameter named 'x'
            Assert.Contains("'Adder'", message);
            Assert.Contains("'x'", message);

            // Using runtime types, and "a"
            CallSite<Func<CallSite, object, object, object, object>> callSiteRuntime =
                CallSite<Func<CallSite, object, object, object, object>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.None, nameof(BaseClass.Adder), Type.EmptyTypes, GetType(),
                        new[]
                        {
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "a"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                        }));

            Assert.Equal(9, callSiteRuntime.Target(callSiteRuntime, new Derived(), 4, 5));
            message = Assert
                .Throws<RuntimeBinderException>(() => callSiteRuntime.Target(callSiteRuntime, new BaseClass(), 4, 5))
                .Message;

            //  The best overload for 'Adder' does not have a parameter named 'a'
            Assert.Contains("'Adder'", message);
            Assert.Contains("'a'", message);

            // Using runtime types, and "x"
            callSiteRuntime = CallSite<Func<CallSite, object, object, object, object>>.Create(
                Binder.InvokeMember(
                    CSharpBinderFlags.None, nameof(BaseClass.Adder), Type.EmptyTypes, GetType(),
                    new[]
                    {
                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "x"),
                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                    }));

            Assert.Equal(9, callSiteRuntime.Target(callSiteRuntime, new BaseClass(), 4, 5));
            message = Assert
                .Throws<RuntimeBinderException>(() => callSiteRuntime.Target(callSiteRuntime, new Derived(), 4, 5))
                .Message;

            //  The best overload for 'Adder' does not have a parameter named 'x'
            Assert.Contains("'Adder'", message);
            Assert.Contains("'x'", message);
        }

        // Tests based on the static tests for Roslyn.
        class C1
        {
            static void M(int a, int b)
            {
                Console.Write($"First {a} {b}. ");
            }

            static void M(long b, long a)
            {
                Console.Write($"Second {b} {a}. ");
            }
        }

        [Fact]
        public void TestSimple()
        {
            Helpers.OverrideConsoleAndRun(console =>
            {
                CallSite<Action<CallSite, Type, int, int>> callsite = CallSite<Action<CallSite, Type, int, int>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C1),
                        new[]
                        {
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType,
                                null),
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                "a"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
                        }));
                callsite.Target(callsite, typeof(C1), 1, 2);
                callsite = CallSite<Action<CallSite, Type, int, int>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C1),
                        new[]
                        {
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType,
                                null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType, "a")
                        }));
                callsite.Target(callsite, typeof(C1), 3, 4);
                Assert.Equal("First 1 2. Second 3 4. ", console.ToString());
            });
        }

        class C2
        {
            C2(int a, int b)
            {
                Console.Write($"{a} {b}.");
            }
        }

        [Fact]
        public void TestSimpleConstructor()
        {
            Helpers.OverrideConsoleAndRun(console =>
            {
                CallSite<Func<CallSite, Type, int, int, C2>> callsite =
                    CallSite<Func<CallSite, Type, int, int, C2>>.Create(
                        Binder.InvokeConstructor(
                            CSharpBinderFlags.None, typeof(C2),
                            new[]
                            {
                                CSharpArgumentInfo.Create(
                                    CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType,
                                    null),
                                CSharpArgumentInfo.Create(
                                    CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.NamedArgument,
                                    "a"),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
                            }));
                callsite.Target(callsite, typeof(C2), 1, 2);
                Assert.Equal("1 2.", console.ToString());
            });
        }

        class C3
        {
            public delegate void MyDelegate(int a, int b);

            public MyDelegate e;

            static void M(int a, int b)
            {
                Console.Write($"{a} {b}. ");
            }

            public C3()
            {
                e += M;
            }
        }

        [Fact]
        public void TestSimpleDelegate()
        {
            // This test differs from its static equivalent considerably, as some event-related matters aren't as directly
            // applicable. We can use it for checking both direct and deferred delegate invocation.

            Helpers.OverrideConsoleAndRun(console =>
            {
                C3 targetObject = new C3();
                CallSite<Func<CallSite, object, object>> getCallSite = CallSite<Func<CallSite, object, object>>.Create(
                    Binder.GetMember(
                        CSharpBinderFlags.None, "e", typeof(C3),
                        new[] {CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)}));
                object dele = getCallSite.Target(getCallSite, targetObject);
                CallSite<Action<CallSite, object, int, int>> invokeCallSite =
                    CallSite<Action<CallSite, object, int, int>>.Create(
                        Binder.InvokeMember(
                            CSharpBinderFlags.ResultDiscarded, "Invoke", Type.EmptyTypes, typeof(C3),
                            new[]
                            {
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                                CSharpArgumentInfo.Create(
                                    CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                    "a"),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                            }));
                invokeCallSite.Target(invokeCallSite, dele, 1, 2);
                invokeCallSite = CallSite<Action<CallSite, object, int, int>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.ResultDiscarded, "e", Type.EmptyTypes, typeof(C3),
                        new[]
                        {
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                "a"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                        }));
                invokeCallSite.Target(invokeCallSite, targetObject, 1, 2);
                Assert.Equal("1 2. 1 2. ", console.ToString());
            });
        }

        class C4
        {
            int this[int a, int b]
            {
                get
                {
                    Console.Write($"Get {a} {b}. ");
                    return 0;
                }
                set { Console.Write($"Set {a} {b} {value}."); }
            }
        }

        [Fact]
        public void TestSimpleIndexer()
        {
            Helpers.OverrideConsoleAndRun(console =>
            {
                C4 targetObject = new C4();
                CallSite<Func<CallSite, object, int, int, object>> getCallSite =
                    CallSite<Func<CallSite, object, int, int, object>>.Create(
                        Binder.GetIndex(
                            CSharpBinderFlags.None, typeof(C4),
                            new[]
                            {
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                                CSharpArgumentInfo.Create(
                                    CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.NamedArgument,
                                    "a"),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                            }));
                Assert.Equal(0, getCallSite.Target(getCallSite, targetObject, 1, 2));
                CallSite<Func<CallSite, object, int, int, int, object>> setCallSite =
                    CallSite<Func<CallSite, object, int, int, int, object>>.Create(
                        Binder.SetIndex(
                            CSharpBinderFlags.None, typeof(C4),
                            new[]
                            {
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                                CSharpArgumentInfo.Create(
                                    CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.NamedArgument,
                                    "a"),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                            }));
                Assert.Equal(5, setCallSite.Target(setCallSite, targetObject, 3, 4, 5));
                Assert.Equal("Get 1 2. Set 3 4 5.", console.ToString());
            });
        }


        class C5
        {
            public C5()
            {
            }

            int this[int a, int b]
            {
                get { throw null; }
            }

            C5(int a, int b)
            {
            }

            // In the static test this is a local function on Main, but to test that without hunting
            // for the name (likely C.<Main>g__local|3_0, but that's implementation-dependent) to
            // push in with reflection we need the support from the compiler that we have yet to make
            // possible. Cut out the chicken-egg problem and just test on a non-local equivalent.
            void Method(int a, int b)
            {
            }
        }

        [Fact]
        public void TestSimpleError()
        {
            CallSite<Func<CallSite, Type, int, int, C5>> ctorCallsite =
                CallSite<Func<CallSite, Type, int, int, C5>>.Create(
                    Binder.InvokeConstructor(
                        CSharpBinderFlags.None, typeof(C5),
                        new[]
                        {
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType,
                                null),
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.NamedArgument,
                                "b"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
                        }));
            string message = Assert
                .Throws<RuntimeBinderException>(() => ctorCallsite.Target(ctorCallsite, typeof(C5), 1, 2))
                .Message;

            // Named argument 'b' is used out-of-position but is followed by an unnamed argument
            Assert.Contains("'b'", message);
            CallSite<Func<CallSite, object, int, int, object>> getCallSite =
                CallSite<Func<CallSite, object, int, int, object>>.Create(
                    Binder.GetIndex(
                        CSharpBinderFlags.None, typeof(C5),
                        new[]
                        {
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.NamedArgument,
                                "b"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                        }));
            message = Assert.Throws<RuntimeBinderException>(() => getCallSite.Target(getCallSite, new C5(), 1, 2))
                .Message;
            Assert.Contains("'b'", message);
            CallSite<Func<CallSite, object, int, int>> invokeCallSite =
                CallSite<Func<CallSite, object, int, int>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.None, nameof(TypeWithMethods.DoStuff), Type.EmptyTypes, GetType(),
                        new[]
                        {
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "b"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                        }));
            message = Assert.Throws<RuntimeBinderException>(() => getCallSite.Target(invokeCallSite, new C5(), 1, 2))
                .Message;
            Assert.Contains("'b'", message);
        }

        class C6
        {
            static void M(int first, int other)
            {
                System.Console.Write($"{first} {other}");
            }
        }

        [Fact]
        public void TestPositionalUnaffected()
        {
            CallSite<Action<CallSite, Type, int, int>> invokeCallSite =
                CallSite<Action<CallSite, Type, int, int>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.None, "M", Type.EmptyTypes, typeof(C6),
                        new[]
                        {
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType,
                                null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                "first")
                        }));
            string message = Assert
                .Throws<RuntimeBinderException>(() => invokeCallSite.Target(invokeCallSite, typeof(C6), 1, 2))
                .Message;

            // Named argument 'first' specifies a parameter for which a positional argument has already been given
            Assert.Contains("'first'", message);
        }

        class C7
        {
            static void M<T1, T2>(T1 a, T2 b)
            {
                System.Console.Write($"{a} {b}.");
            }
        }

        [Fact]
        public void TestGenericInference()
        {
            Helpers.OverrideConsoleAndRun(console =>
            {

                // Test with types defined fully.
                CallSite<Action<CallSite, Type, int, string>> compileTimeTypeCallSite =
                    CallSite<Action<CallSite, Type, int, string>>.Create(
                        Binder.InvokeMember(
                            CSharpBinderFlags.ResultDiscarded, "M", new[] {typeof(int), typeof(string)}, typeof(C7),
                            new[]
                            {
                                CSharpArgumentInfo.Create(
                                    CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType,
                                    null),
                                CSharpArgumentInfo.Create(
                                    CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                    "a"),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
                            }));
                compileTimeTypeCallSite.Target(compileTimeTypeCallSite, typeof(C7), 1, "hi");
                Assert.Equal("1 hi.", console.ToString());

                // Test with types defined fully in delegate but generic types deduced.
                console.GetStringBuilder().Length = 0;
                compileTimeTypeCallSite = CallSite<Action<CallSite, Type, int, string>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C7),
                        new[]
                        {
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType,
                                null),
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                "a"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
                        }));
                compileTimeTypeCallSite.Target(compileTimeTypeCallSite, typeof(C7), 1, "hi");
                Assert.Equal("1 hi.", console.ToString());

                // Test with all types deduced by the dynamic binder.
                console.GetStringBuilder().Length = 0;
                CallSite<Action<CallSite, Type, object, object>> runtimeTypeCallSite =
                    CallSite<Action<CallSite, Type, object, object>>.Create(
                        Binder.InvokeMember(
                            CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C7),
                            new[]
                            {
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.IsStaticType, null),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "a"),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
                            }));
                runtimeTypeCallSite.Target(runtimeTypeCallSite, typeof(C7), 1, "hi");
                Assert.Equal("1 hi.", console.ToString());
            });
        }

        class C8
        {
            static void M(int a, int b, int c = 1)
            {
                System.Console.Write($"M {a} {b}");
            }
        }

        [Fact]
        public void TestPositionalUnaffected2()
        {
            CallSite<Action<CallSite, Type, int, int>> callSite = CallSite<Action<CallSite, Type, int, int>>.Create(
                Binder.InvokeMember(
                    CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C8),
                    new[]
                    {
                        CSharpArgumentInfo.Create(
                            CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType, null),
                        CSharpArgumentInfo.Create(
                            CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType, "c"),
                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
                    }));
            string message = Assert.Throws<RuntimeBinderException>(() => callSite.Target(callSite, typeof(C8), 1, 2))
                .Message;

            //  Named argument 'c' is used out-of-position but is followed by an unnamed argument
            Assert.Contains("'c'", message);
        }

        class C9
        {
            static void M(params int[] x)
            {
            }
        }

        [Fact]
        public void TestNamedParams()
        {
            CallSite<Action<CallSite, Type, int, int>> callSite = CallSite<Action<CallSite, Type, int, int>>.Create(
                Binder.InvokeMember(
                    CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C9),
                    new[]
                    {
                        CSharpArgumentInfo.Create(
                            CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType, null),
                        CSharpArgumentInfo.Create(
                            CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType, "x"),
                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
                    }));

            string message = Assert.Throws<RuntimeBinderException>(() => callSite.Target(callSite, typeof(C9), 1, 2))
                .Message;

            // No overload for method 'M' takes 2 arguments
            Assert.Contains("'M'", message);
            Assert.Contains(" 2 ", message);
        }

        [Fact]
        public void TestNamedParams2()
        {
            CallSite<Action<CallSite, Type, int, int>> callSite = CallSite<Action<CallSite, Type, int, int>>.Create(
                Binder.InvokeMember(
                    CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C9),
                    new[]
                    {
                        CSharpArgumentInfo.Create(
                            CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType, null),
                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                        CSharpArgumentInfo.Create(
                            CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType, "x")
                    }));

            string message = Assert.Throws<RuntimeBinderException>(() => callSite.Target(callSite, typeof(C9), 1, 2))
                .Message;

            // Named argument 'x' specifies a parameter for which a positional argument has already been given
            Assert.Contains("'x'", message);
        }

        class C10
        {
            static void M(int x, params string[] y)
            {
                System.Console.Write($"{x} {string.Join(",", y)}. ");
            }
        }

        [Fact]
        public void TestNamedParamsVariousForms()
        {
            // This extends the static test in also calling with no arguments for the params section.
            Helpers.OverrideConsoleAndRun(console =>
            {

                CallSite<Action<CallSite, Type, int, string>> callSite0 =
                    CallSite<Action<CallSite, Type, int, string>>.Create(
                        Binder.InvokeMember(
                            CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C10),
                            new[]
                            {
                                CSharpArgumentInfo.Create(
                                    CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType,
                                    null),
                                CSharpArgumentInfo.Create(
                                    CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                    "x"),
                                CSharpArgumentInfo.Create(
                                    CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                    "y")
                            }));
                callSite0.Target(callSite0, typeof(C10), 1, "2");

                callSite0 = CallSite<Action<CallSite, Type, int, string>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C10),
                        new[]
                        {
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType,
                                null),
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                "x"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
                        }));
                callSite0.Target(callSite0, typeof(C10), 2, "3");

                CallSite<Action<CallSite, Type, int, string[]>> callSite1 =
                    CallSite<Action<CallSite, Type, int, string[]>>.Create(
                        Binder.InvokeMember(
                            CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C10),
                            new[]
                            {
                                CSharpArgumentInfo.Create(
                                    CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType,
                                    null),
                                CSharpArgumentInfo.Create(
                                    CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                    "x"),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
                            }));
                callSite1.Target(callSite1, typeof(C10), 3, new[] {"4", "5"});

                CallSite<Action<CallSite, Type, int>> callSite2 = CallSite<Action<CallSite, Type, int>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C10),
                        new[]
                        {
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType,
                                null),
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType, "x")
                        }));
                callSite2.Target(callSite2, typeof(C10), 4);

                Assert.Equal("1 2. 2 3. 3 4,5. 4 . ", console.ToString());
            });
        }

        [Fact]
        public void TestNamedParamsVariousFormsDynamicallyDeducedTypes()
        {
            // This extends the static test in also calling with no arguments for the params section.
            Helpers.OverrideConsoleAndRun(console =>
            {

                CallSite<Action<CallSite, Type, object, object>> callSite0 =
                    CallSite<Action<CallSite, Type, object, object>>.Create(
                        Binder.InvokeMember(
                            CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C10),
                            new[]
                            {
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.IsStaticType, null),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "x"),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "y")
                            }));
                callSite0.Target(callSite0, typeof(C10), 1, "2");

                callSite0 = CallSite<Action<CallSite, Type, object, object>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C10),
                        new[]
                        {
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.IsStaticType, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "x"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                        }));
                callSite0.Target(callSite0, typeof(C10), 2, "3");

                CallSite<Action<CallSite, Type, object, object>> callSite1 =
                    CallSite<Action<CallSite, Type, object, object>>.Create(
                        Binder.InvokeMember(
                            CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C10),
                            new[]
                            {
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.IsStaticType, null),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "x"),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                            }));
                callSite1.Target(callSite1, typeof(C10), 3, new[] {"4", "5"});

                CallSite<Action<CallSite, Type, object>> callSite2 = CallSite<Action<CallSite, Type, object>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C10),
                        new[]
                        {
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.IsStaticType, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "x")
                        }));
                callSite2.Target(callSite2, typeof(C10), 4);

                Assert.Equal("1 2. 2 3. 3 4,5. 4 . ", console.ToString());
            });
        }

        [Fact]
        public void TestTwiceNamedParams()
        {
            CallSite<Action<CallSite, Type, int, int>> callSite = CallSite<Action<CallSite, Type, int, int>>.Create(
                Binder.InvokeMember(
                    CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C9),
                    new[]
                    {
                        CSharpArgumentInfo.Create(
                            CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType, null),
                        CSharpArgumentInfo.Create(
                            CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType, "x"),
                        CSharpArgumentInfo.Create(
                            CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType, "x")
                    }));

            string message = Assert.Throws<RuntimeBinderException>(() => callSite.Target(callSite, typeof(C9), 1, 2))
                .Message;

            // Named argument 'x' cannot be specified multiple times
            Assert.Contains("'x'", message);
        }

        class C11
        {
            static void M(int x, params int[] y)
            {
            }
        }

        [Fact]
        public void TestNamedParams3()
        {
            CallSite<Action<CallSite, Type, int, int>> callSite = CallSite<Action<CallSite, Type, int, int>>.Create(
                Binder.InvokeMember(
                    CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C11),
                    new[]
                    {
                        CSharpArgumentInfo.Create(
                            CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType, null),
                        CSharpArgumentInfo.Create(
                            CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType, "y"),
                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
                    }));

            string message = Assert.Throws<RuntimeBinderException>(() => callSite.Target(callSite, typeof(C11), 1, 2))
                .Message;

            // Named argument 'y' is used out-of-position but is followed by an unnamed argument
            Assert.Contains("'y'", message);
        }

        [Fact]
        public void TestNamedParams4()
        {
            CallSite<Action<CallSite, Type, int, int, int>> callSite =
                CallSite<Action<CallSite, Type, int, int, int>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C11),
                        new[]
                        {
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType,
                                null),
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                "x"),
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                "y"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
                        }));

            string message = Assert
                .Throws<RuntimeBinderException>(() => callSite.Target(callSite, typeof(C11), 1, 2, 3))
                .Message;

            // No overload for method 'M' takes 3 arguments
            Assert.Contains("'M'", message);
            Assert.Contains(" 3 ", message);
        }

        class C12
        {
            static void M(int x, params int[] y)
            {
                System.Console.Write($"x={x} y[0]={y[0]} y.Length={y.Length}");
            }
        }

        [Fact]
        public void TestNamedParams5()
        {
            Helpers.OverrideConsoleAndRun(console =>
            {
                CallSite<Action<CallSite, Type, int, int>> callSite = CallSite<Action<CallSite, Type, int, int>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C12),
                        new[]
                        {
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType,
                                null),
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                "y"),
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType, "x")
                        }));
                callSite.Target(callSite, typeof(C12), 1, 2);
                Assert.Equal("x=2 y[0]=1 y.Length=1", console.ToString());
            });
        }

        class C13
        {
            static void M(int a = 1, int b = 2, int c = 3)
            {
            }
        }

        [Fact]
        public void TestBadNonTrailing()
        {
            CallSite<Action<CallSite, Type, int, int>> callSite = CallSite<Action<CallSite, Type, int, int>>.Create(
                Binder.InvokeMember(
                    CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C13),
                    new[]
                    {
                        CSharpArgumentInfo.Create(
                            CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType, null),
                        CSharpArgumentInfo.Create(
                            CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType, "c"),
                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
                    }));
            string message = Assert.Throws<RuntimeBinderException>(() => callSite.Target(callSite, typeof(C13), 3, 2)).Message;
            //Named argument 'c' is used out-of-position but is followed by an unnamed argument
            Assert.Contains(" 'c' ", message);
        }

        class C14
        {
            static void M(int a = 1, int b = 2, int c = 3)
            {
            }
            static void M(long c = 1, long b = 2)
            {
                System.Console.Write($"Second {c} {b}. ");
            }
        }

        [Fact]
        public void TestPickGoodOverload()
        {
            Helpers.OverrideConsoleAndRun(console =>
            {
                CallSite<Action<CallSite, Type, int, int>> callSite = CallSite<Action<CallSite, Type, int, int>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C14),
                        new[]
                        {
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType,
                                null),
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                "c"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
                        }));
                callSite.Target(callSite, typeof(C14), 3, 2);
                Assert.Equal("Second 3 2. ", console.ToString());
            });
        }

        [Fact]
        public void TestPickGoodOverloadDynamicallyTyped()
        {
            Helpers.OverrideConsoleAndRun(console =>
            {
                CallSite<Action<CallSite, Type, object, object>> callSite =
                    CallSite<Action<CallSite, Type, object, object>>.Create(
                        Binder.InvokeMember(
                            CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C14),
                            new[]
                            {
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.IsStaticType, null),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "c"),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                            }));
                callSite.Target(callSite, typeof(C14), 3, 2);
                Assert.Equal("Second 3 2. ", console.ToString());
            });
        }

        class C15
        {
            static void M(long a = 1, long b = 2, long c = 3)
            {
            }
            static void M(int c = 1, int b = 2)
            {
                System.Console.Write($"Second {c} {b}.");
            }
        }

        [Fact]
        public void TestPickGoodOverload2()
        {
            Helpers.OverrideConsoleAndRun(console =>
            {
                CallSite<Action<CallSite, Type, int, int>> callSite = CallSite<Action<CallSite, Type, int, int>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C15),
                        new[]
                        {
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType,
                                null),
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                "c"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
                        }));
                callSite.Target(callSite, typeof(C15), 3, 2);
                Assert.Equal("Second 3 2.", console.ToString());
            });
        }

        [Fact]
        public void TestPickGoodOverload2DynamicallyTyped()
        {
            Helpers.OverrideConsoleAndRun(console =>
            {
                CallSite<Action<CallSite, Type, object, object>> callSite =
                    CallSite<Action<CallSite, Type, object, object>>.Create(
                        Binder.InvokeMember(
                            CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C15),
                            new[]
                            {
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.IsStaticType, null),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "c"),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                            }));
                callSite.Target(callSite, typeof(C15), 3, 2);
                Assert.Equal("Second 3 2.", console.ToString());
            });
        }

        class C16
        {
            static void M(int a, int b, int c = 42)
            {
                System.Console.Write(c);
            }
        }

        [Fact]
        public void TestOptionalValues()
        {
            Helpers.OverrideConsoleAndRun(console =>
            {
                CallSite<Action<CallSite, Type, int, int>> callSite = CallSite<Action<CallSite, Type, int, int>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C16),
                        new[]
                        {
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType,
                                null),
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                "a"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
                        }));
                callSite.Target(callSite, typeof(C16), 1, 2);
                Assert.Equal("42", console.ToString());
            });
        }

        [Fact]
        public void TestOptionalValuesRunTimeTypes()
        {
            Helpers.OverrideConsoleAndRun(console =>
            {
                CallSite<Action<CallSite, Type, object, object>> callSite =
                    CallSite<Action<CallSite, Type, object, object>>.Create(
                        Binder.InvokeMember(
                            CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C16),
                            new[]
                            {
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.IsStaticType, null),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "a"),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                            }));
                callSite.Target(callSite, typeof(C16), 1, 2);
                Assert.Equal("42", console.ToString());
            });
        }

        class C17
        {
            static void M(int a, int b, params int[] c)
            {
            }
        }

        [Fact]
        public void TestParams()
        {
            CallSite<Action<CallSite, Type, int, int, int>> callSite =
                CallSite<Action<CallSite, Type, int, int, int>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C17),
                        new[]
                        {
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType,
                                null),
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                "b"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
                        }));
            string message = Assert
                .Throws<RuntimeBinderException>(() => callSite.Target(callSite, typeof(C17), 2, 3, 4))
                .Message;
            // Named argument 'b' is used out-of-position but is followed by an unnamed argument
            Assert.Contains(" 'b' ", message);
        }

        [Fact]
        public void TestParamsRuntimeTypes()
        {
            CallSite<Action<CallSite, Type, object, object, object>> callSite =
                CallSite<Action<CallSite, Type, object, object, object>>.Create(
                    Binder.InvokeMember(
                        CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C17),
                        new[]
                        {
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.IsStaticType, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "b"),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                        }));
            string message = Assert
                .Throws<RuntimeBinderException>(() => callSite.Target(callSite, typeof(C17), 2, 3, 4))
                .Message;

            // Named argument 'b' is used out-of-position but is followed by an unnamed argument
            Assert.Contains(" 'b' ", message);
        }

        class C18
        {
            static void M(int a, int b, params int[] c)
            {
                System.Console.Write($"{a} {b} {c[0]} {c[1]} Length:{c.Length}");
            }
        }

        [Fact]
        public void TestParams2()
        {
            Helpers.OverrideConsoleAndRun(console =>
            {
                CallSite<Action<CallSite, Type, int, int, int, int>> callSite =
                    CallSite<Action<CallSite, Type, int, int, int, int>>.Create(
                        Binder.InvokeMember(
                            CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C18),
                            new[]
                            {
                                CSharpArgumentInfo.Create(
                                    CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType,
                                    null),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                                CSharpArgumentInfo.Create(
                                    CSharpArgumentInfoFlags.NamedArgument | CSharpArgumentInfoFlags.UseCompileTimeType,
                                    "b"),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
                            }));
                callSite.Target(callSite, typeof(C18), 1, 2, 3, 4);
                Assert.Equal("1 2 3 4 Length:2", console.ToString());
            });
        }

        [Fact]
        public void TestParams2RuntimeTypes()
        {
            Helpers.OverrideConsoleAndRun(console =>
            {
                CallSite<Action<CallSite, Type, object, object, object, object>> callSite =
                    CallSite<Action<CallSite, Type, object, object, object, object>>.Create(
                        Binder.InvokeMember(
                            CSharpBinderFlags.ResultDiscarded, "M", Type.EmptyTypes, typeof(C18),
                            new[]
                            {
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.IsStaticType, null),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, "b"),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                            }));
                callSite.Target(callSite, typeof(C18), 1, 2, 3, 4);
                Assert.Equal("1 2 3 4 Length:2", console.ToString());
            });
        }
    }
}
