package org.raku.rakudo;

import java.util.*;

import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;

import org.raku.nqp.runtime.*;
import org.raku.nqp.sixmodel.*;
import org.raku.nqp.sixmodel.reprs.ContextRefInstance;
import org.raku.nqp.sixmodel.reprs.P6int;
import org.raku.nqp.sixmodel.reprs.P6str;
import org.raku.nqp.sixmodel.reprs.P6num;
import org.raku.nqp.sixmodel.reprs.P6OpaqueREPRData;

@SuppressWarnings("unused")
public final class Binder {
    /* Possible results of binding. */
    public static final int BIND_RESULT_OK       = 0;
    public static final int BIND_RESULT_FAIL     = 1;
    public static final int BIND_RESULT_JUNCTION = 2;

    /* Compile time trial binding result indicators. */
    public static final int TRIAL_BIND_NOT_SURE =  0;  /* Plausible, but need to check at runtime. */
    public static final int TRIAL_BIND_OK       =  1;  /* Bind will always work out. */
    public static final int TRIAL_BIND_NO_WAY   = -1;  /* Bind could never work out. */

    /* Flags. */
    private static final int SIG_ELEM_BIND_CAPTURE        = 1;
    private static final int SIG_ELEM_BIND_PRIVATE_ATTR   = 2;
    private static final int SIG_ELEM_BIND_PUBLIC_ATTR    = 4;
    private static final int SIG_ELEM_BIND_ATTRIBUTIVE    = (SIG_ELEM_BIND_PRIVATE_ATTR | SIG_ELEM_BIND_PUBLIC_ATTR);
    private static final int SIG_ELEM_SLURPY_POS          = 8;
    private static final int SIG_ELEM_SLURPY_NAMED        = 16;
    private static final int SIG_ELEM_SLURPY_LOL          = 32;
    private static final int SIG_ELEM_INVOCANT            = 64;
    private static final int SIG_ELEM_MULTI_INVOCANT      = 128;
    private static final int SIG_ELEM_IS_RW               = 256;
    private static final int SIG_ELEM_IS_COPY             = 512;
    private static final int SIG_ELEM_IS_RAW              = 1024;
    private static final int SIG_ELEM_IS_OPTIONAL         = 2048;
    private static final int SIG_ELEM_ARRAY_SIGIL         = 4096;
    private static final int SIG_ELEM_HASH_SIGIL          = 8192;
    private static final int SIG_ELEM_DEFAULT_FROM_OUTER  = 16384;
    private static final int SIG_ELEM_IS_CAPTURE          = 32768;
    private static final int SIG_ELEM_UNDEFINED_ONLY      = 65536;
    private static final int SIG_ELEM_DEFINED_ONLY        = 131072;
    private static final int SIG_ELEM_DEFINEDNES_CHECK    = (SIG_ELEM_UNDEFINED_ONLY | SIG_ELEM_DEFINED_ONLY);
    private static final int SIG_ELEM_TYPE_GENERIC        = 524288;
    private static final int SIG_ELEM_DEFAULT_IS_LITERAL  = 1048576;
    private static final int SIG_ELEM_NATIVE_INT_VALUE    = 2097152;
    private static final int SIG_ELEM_NATIVE_UINT_VALUE   = 134217728;
    private static final int SIG_ELEM_NATIVE_NUM_VALUE    = 4194304;
    private static final int SIG_ELEM_NATIVE_STR_VALUE    = 8388608;
    private static final int SIG_ELEM_NATIVE_VALUE        = (SIG_ELEM_NATIVE_INT_VALUE | SIG_ELEM_NATIVE_UINT_VALUE | SIG_ELEM_NATIVE_NUM_VALUE | SIG_ELEM_NATIVE_STR_VALUE);
    private static final int SIG_ELEM_SLURPY_ONEARG       = 16777216;
    private static final int SIG_ELEM_SLURPY              = (SIG_ELEM_SLURPY_POS | SIG_ELEM_SLURPY_NAMED | SIG_ELEM_SLURPY_LOL | SIG_ELEM_SLURPY_ONEARG);
    private static final int SIG_ELEM_CODE_SIGIL          = 33554432;
    private static final int SIG_ELEM_IS_COERCIVE         = 67108864;

    /* Hints for Parameter attributes. */
    private static final int HINT_variable_name = 0;
    private static final int HINT_named_names = 1;
    private static final int HINT_type_captures = 2;
    private static final int HINT_flags = 3;
    private static final int HINT_type = 4;
    private static final int HINT_post_constraints = 5;
    private static final int HINT_sub_signature = 6;
    private static final int HINT_default_value = 7;
    private static final int HINT_container_descriptor = 8;
    private static final int HINT_attr_package = 9;

    /* Other hints. */
    private static final int HINT_ENUMMAP_storage = 0;
    private static final int HINT_CAPTURE_list = 0;
    private static final int HINT_CAPTURE_hash = 1;
    private static final int HINT_LIST_reified = 0;
    private static final int HINT_SIG_params = 0;

    private static SixModelObject createBox(ThreadContext tc, RakOps.GlobalExt gcx, Object arg, int flag) {
        switch (flag) {
            case CallSiteDescriptor.ARG_INT:
                return Ops.box_i((long)arg, gcx.Int, tc);
            case CallSiteDescriptor.ARG_UINT:
                return Ops.box_u((long)arg, gcx.Int, tc);
            case CallSiteDescriptor.ARG_NUM:
                return Ops.box_n((double)arg, gcx.Num, tc);
            case CallSiteDescriptor.ARG_STR:
                return Ops.box_s((String)arg, gcx.Str, tc);
            default:
                throw new RuntimeException("Impossible case reached in createBox");
        }
    }

    private static String arityFail(ThreadContext tc, RakOps.GlobalExt gcx, CallFrame cf,
            SixModelObject params, int numParams, int numPosArgs, boolean tooMany) {
        int arity = 0;
        int count = 0;
        String fail = tooMany ? "Too many" : "Too few";

        /* Work out how many we could have been passed. */
        for (int i = 0; i < numParams; i++) {
            SixModelObject param = params.at_pos_boxed(tc, i);
            param.get_attribute_native(tc, gcx.Parameter, "$!flags", HINT_flags);
            int flags = (int)tc.native_i;
            SixModelObject namedNames = param.get_attribute_boxed(tc,
                gcx.Parameter, "@!named_names", HINT_named_names);

            if (namedNames != null)
                continue;
            if ((flags & SIG_ELEM_SLURPY_NAMED) != 0)
                continue;
            if ((flags & SIG_ELEM_SLURPY) != 0) {
                count = -1000; // cargo-culted from BOOTSTRAP.nqp: "in case a pos can sneak past a slurpy somehow"
            }
            else if ((flags & SIG_ELEM_IS_OPTIONAL) != 0) {
                count++;
            }
            else {
                count++;
                arity++;
            }
        }

        String routineName = cf.codeRef.name;
        if (routineName == null || routineName.isEmpty())
            routineName = "<anon>";

        /* Now generate decent error. */
        if (arity == count)
            return String.format(
                "%s positionals passed to '%s'; expected %d arguments but got %d",
                fail, routineName, arity, numPosArgs);
        else if (count <= -1)
            return String.format(
                "%s positionals passed to '%s'; expected at least %d arguments but got only %d",
                fail, routineName, arity, numPosArgs);
        else
            return String.format(
                "%s positionals passed to '%s'; expected %d %s %d arguments but got %d",
                fail, routineName, arity, arity + 1 == count ? "or" : "to" , count, numPosArgs);
    }

    /* Binds any type captures. */
    public static void bindTypeCaptures(ThreadContext tc, SixModelObject typeCaps, CallFrame cf, SixModelObject type) {
        long elems = typeCaps.elems(tc);
        StaticCodeInfo sci = cf.codeRef.staticInfo;
        for (long i = 0; i < elems; i++) {
            typeCaps.at_pos_native(tc, i);
            String name = tc.native_s;
            cf.oLex[sci.oTryGetLexicalIdx(name)] = type;
        }
    }

    /* Assigns an attributive parameter to the desired attribute. */
    private static int assignAttributive(ThreadContext tc, CallFrame cf, String varName,
            int paramFlags, SixModelObject attrPackage, SixModelObject value, Object[] error) {
        /* Find self. */
        StaticCodeInfo sci = cf.codeRef.staticInfo;
        int selfIdx = sci.oTryGetLexicalIdx("self");
        SixModelObject self = null;
        if (selfIdx == -1) {
            self = Ops.getlexouter("self", tc);
            if (self == null) {
                if (error != null)
                    error[0] = String.format(
                        "Unable to bind attributive parameter '%s' - could not find self",
                        varName);
                return BIND_RESULT_FAIL;
            }
        }
        else {
            self = cf.oLex[selfIdx];
        }

        /* If it's private, just need to fetch the attribute. */
        SixModelObject assignee;
        if ((paramFlags & SIG_ELEM_BIND_PRIVATE_ATTR) != 0) {
            /* If we have a native Attribute we can't get a container for it, and
               since *trying* to get a container would throw already, we first check
               if the target Attribute is native. */
            int hint = -1;
            for (Object2IntOpenHashMap<String> map : ((P6OpaqueREPRData) (attrPackage.st.REPRData)).nameToHintMap) {
                hint = map.getOrDefault(varName, -1);
            }
            REPR attrREPR = null;
            if (((P6OpaqueREPRData) (attrPackage.st.REPRData)).flattenedSTables[hint] != null) {
                /* We sometimes don't have flattenedSTables. I'm not sure that's okay, honestly... */
                attrREPR = ((P6OpaqueREPRData) (attrPackage.st.REPRData)).flattenedSTables[hint].REPR;
            }
            if (attrREPR instanceof P6int) {
                Ops.bindattr_i(self, attrPackage, varName, Ops.unbox_i(value, tc), tc);
            }
            else if (attrREPR instanceof P6num) {
                Ops.bindattr_n(self, attrPackage, varName, Ops.unbox_n(value, tc), tc);
            }
            else if (attrREPR instanceof P6str) {
                Ops.bindattr_s(self, attrPackage, varName, Ops.unbox_s(value, tc), tc);
            }
            else {
                /* ...but we'll just assume it's probably some boxed Attribute. */
                assignee = self.get_attribute_boxed(tc, attrPackage, varName, STable.NO_HINT);
                RakOps.p6store(assignee, value, tc);
            }
        }
        /* Otherwise if it's public, do a method call to get the assignee. */
        else {
            throw new RuntimeException("$.x parameters NYI");
        }
        return BIND_RESULT_OK;
    }

    /* Returns an appropriate failure mode (junction fail or normal fail). */
    private static int juncOrFail(ThreadContext tc, RakOps.GlobalExt gcx, SixModelObject value) {
        if (value.st.WHAT == gcx.Junction && Ops.isconcrete(value, tc) != 0)
            return BIND_RESULT_JUNCTION;
        else
            return BIND_RESULT_FAIL;
    }

    /* Binds a single argument into the lexpad, after doing any checks that are
     * needed. Also handles any type captures. If there is a sub signature, then
     * re-enters the binder. Returns one of the BIND_RESULT_* codes. */
    private static final CallSiteDescriptor genIns = new CallSiteDescriptor(
        new byte[] { CallSiteDescriptor.ARG_OBJ, CallSiteDescriptor.ARG_OBJ, CallSiteDescriptor.ARG_OBJ }, null);
    private static final CallSiteDescriptor targetType = new CallSiteDescriptor(
        new byte[] { CallSiteDescriptor.ARG_OBJ, CallSiteDescriptor.ARG_OBJ }, null);
    private static final CallSiteDescriptor ACCEPTS_o = new CallSiteDescriptor(
        new byte[] { CallSiteDescriptor.ARG_OBJ, CallSiteDescriptor.ARG_OBJ }, null);
    private static final CallSiteDescriptor ACCEPTS_i = new CallSiteDescriptor(
        new byte[] { CallSiteDescriptor.ARG_OBJ, CallSiteDescriptor.ARG_INT }, null);
    private static final CallSiteDescriptor ACCEPTS_u = new CallSiteDescriptor(
        new byte[] { CallSiteDescriptor.ARG_OBJ, CallSiteDescriptor.ARG_UINT }, null);
    private static final CallSiteDescriptor ACCEPTS_n = new CallSiteDescriptor(
        new byte[] { CallSiteDescriptor.ARG_OBJ, CallSiteDescriptor.ARG_NUM }, null);
    private static final CallSiteDescriptor ACCEPTS_s = new CallSiteDescriptor(
        new byte[] { CallSiteDescriptor.ARG_OBJ, CallSiteDescriptor.ARG_STR }, null);
    private static final CallSiteDescriptor bindParamThrower = new CallSiteDescriptor(
        new byte[] { CallSiteDescriptor.ARG_OBJ, CallSiteDescriptor.ARG_OBJ,
            CallSiteDescriptor.ARG_STR, CallSiteDescriptor.ARG_OBJ,
            CallSiteDescriptor.ARG_INT
        }, null);
    private static final CallSiteDescriptor bindConcreteThrower = new CallSiteDescriptor(
        new byte[] { CallSiteDescriptor.ARG_STR, CallSiteDescriptor.ARG_STR,
            CallSiteDescriptor.ARG_STR, CallSiteDescriptor.ARG_STR,
            CallSiteDescriptor.ARG_INT, CallSiteDescriptor.ARG_INT
        }, null);
    private static final CallSiteDescriptor paramReadWriteThrower = new CallSiteDescriptor(
        new byte[] { CallSiteDescriptor.ARG_OBJ, CallSiteDescriptor.ARG_STR }, null);
    private static int bindOneParam(ThreadContext tc, RakOps.GlobalExt gcx, CallFrame cf, SixModelObject param,
            Object origArg, byte origFlag, boolean noNomTypeCheck, boolean isSlurpyNamed, Object[] error) {
        /* Get parameter flags and variable name. */
        param.get_attribute_native(tc, gcx.Parameter, "$!flags", HINT_flags);
        int paramFlags = (int)tc.native_i;
        param.get_attribute_native(tc, gcx.Parameter, "$!variable_name", HINT_variable_name);
        String varName = tc.native_s;
        boolean hasVarName = true;
        if (varName == null || varName.isEmpty()) {
            varName = "<anon>";
            hasVarName = false;
        }
        if (RakOps.DEBUG_MODE)
            System.err.println(varName);

        /* We'll put the value to bind into one of the following locals, and
         * flag will indicate what type of thing it is. */
        int flag;
        long arg_i = 0;
        double arg_n = 0.0;
        String arg_s = null;
        SixModelObject arg_o = null;

        /* Check if boxed/unboxed expectations are met. */
        int desiredNative = paramFlags & SIG_ELEM_NATIVE_VALUE;
        boolean is_rw = (paramFlags & SIG_ELEM_IS_RW) != 0;
        int gotNative = origFlag & (CallSiteDescriptor.ARG_INT | CallSiteDescriptor.ARG_UINT | CallSiteDescriptor.ARG_NUM | CallSiteDescriptor.ARG_STR);
        if (is_rw && desiredNative != 0) {
            switch (desiredNative) {
            case SIG_ELEM_NATIVE_INT_VALUE:
                if (gotNative != 0 || Ops.iscont_i((SixModelObject)origArg) == 0) {
                    if (error != null)
                        error[0] = String.format(
                            "Expected a modifiable native int argument for '%s'",
                            varName);
                    return BIND_RESULT_FAIL;
                }
                break;
            case SIG_ELEM_NATIVE_UINT_VALUE:
                if (gotNative != 0 || Ops.iscont_u((SixModelObject)origArg) == 0) {
                    if (error != null)
                        error[0] = String.format(
                            "Expected a modifiable native unsigned int argument for '%s'",
                            varName);
                    return BIND_RESULT_FAIL;
                }
                break;
            case SIG_ELEM_NATIVE_NUM_VALUE:
                if (gotNative != 0 || Ops.iscont_n((SixModelObject)origArg) == 0) {
                    if (error != null)
                        error[0] = String.format(
                            "Expected a modifiable native num argument for '%s'",
                            varName);
                    return BIND_RESULT_FAIL;
                }
                break;
            case SIG_ELEM_NATIVE_STR_VALUE:
                if (gotNative != 0 || Ops.iscont_s((SixModelObject)origArg) == 0) {
                    if (error != null)
                        error[0] = String.format(
                            "Expected a modifiable native str argument for '%s'",
                            varName);
                    return BIND_RESULT_FAIL;
                }
                break;
            }
            flag = CallSiteDescriptor.ARG_OBJ;
            arg_o = (SixModelObject)origArg;
        }
        else if (desiredNative == 0 && gotNative == CallSiteDescriptor.ARG_OBJ) {
            flag = gotNative;
            arg_o = (SixModelObject)origArg;
        }
        else if (desiredNative == SIG_ELEM_NATIVE_INT_VALUE && gotNative == CallSiteDescriptor.ARG_INT) {
            flag = gotNative;
            arg_i = (long)origArg;
        }
        else if (desiredNative == SIG_ELEM_NATIVE_UINT_VALUE && gotNative == CallSiteDescriptor.ARG_UINT) {
            flag = gotNative;
            arg_i = (long)origArg;
        }
        else if (desiredNative == SIG_ELEM_NATIVE_NUM_VALUE && gotNative == CallSiteDescriptor.ARG_NUM) {
            flag = gotNative;
            arg_n = (double)origArg;
        }
        else if (desiredNative == SIG_ELEM_NATIVE_STR_VALUE && gotNative == CallSiteDescriptor.ARG_STR) {
            flag = gotNative;
            arg_s = (String)origArg;
        }
        else if (desiredNative == 0) {
            /* We need to do a boxing operation. */
            flag = CallSiteDescriptor.ARG_OBJ;
            arg_o = createBox(tc, gcx, origArg, gotNative);
        }
        else {
            /* We need to do an unboxing operation. */
            SixModelObject decontValue = Ops.decont((SixModelObject)origArg, tc);
            StorageSpec spec = decontValue.st.REPR.get_storage_spec(tc, decontValue.st);
            switch (desiredNative) {
                case SIG_ELEM_NATIVE_INT_VALUE:
                    if ((spec.can_box & StorageSpec.CAN_BOX_INT) != 0) {
                        flag = CallSiteDescriptor.ARG_INT;
                        arg_i = decontValue.get_int(tc);
                    }
                    else {
                        if (error != null)
                            error[0] = String.format(
                                "Cannot unbox argument to '%s' as a native int",
                                varName);
                        return BIND_RESULT_FAIL;
                    }
                    break;
                case SIG_ELEM_NATIVE_UINT_VALUE:
                    if ((spec.can_box & StorageSpec.CAN_BOX_INT) != 0) {
                        flag = CallSiteDescriptor.ARG_UINT;
                        arg_i = decontValue.get_int(tc);
                    }
                    else {
                        if (error != null)
                            error[0] = String.format(
                                "Cannot unbox argument to '%s' as a native int",
                                varName);
                        return BIND_RESULT_FAIL;
                    }
                    break;
                case SIG_ELEM_NATIVE_NUM_VALUE:
                    if ((spec.can_box & StorageSpec.CAN_BOX_NUM) != 0) {
                        flag = CallSiteDescriptor.ARG_NUM;
                        arg_n = decontValue.get_num(tc);
                    }
                    else {
                        if (error != null)
                            error[0] = String.format(
                                "Cannot unbox argument to '%s' as a native num",
                                varName);
                        return BIND_RESULT_FAIL;
                    }
                    break;
                case SIG_ELEM_NATIVE_STR_VALUE:
                    if ((spec.can_box & StorageSpec.CAN_BOX_STR) != 0) {
                        flag = CallSiteDescriptor.ARG_STR;
                        arg_s = decontValue.get_str(tc);
                    }
                    else {
                        if (error != null)
                            error[0] = String.format(
                                "Cannot unbox argument to '%s' as a native str",
                                varName);
                        return BIND_RESULT_FAIL;
                    }
                    break;
                default:
                    if (error != null)
                        error[0] = String.format(
                            "Cannot unbox argument to '%s' as a native type",
                            varName);
                    return BIND_RESULT_FAIL;
            }
        }

        /* By this point, we'll either have an object that we might be able to
         * bind if it passes the type check, or a native value that needs no
         * further checking. */
        SixModelObject decontValue = null;
        boolean didHLLTransform = false;
        SixModelObject paramType = param.get_attribute_boxed(tc, gcx.Parameter, "$!type", HINT_type);
        SixModelObject ContextRef = null;
        SixModelObject HOW = null;
        if (flag == CallSiteDescriptor.ARG_OBJ && !(is_rw && desiredNative != 0)) {
            /* We need to work on the decontainerized value. */
            decontValue = Ops.decont(arg_o, tc);

            /* HLL map it as needed. */
            SixModelObject beforeHLLize = decontValue;
            decontValue = Ops.hllize(decontValue, tc);
            if (decontValue != beforeHLLize)
                didHLLTransform = true;

            /* Skip nominal type check if not needed. */
            if (!noNomTypeCheck) {
                /* Is the nominal type generic and in need of instantiation? (This
                 * can happen in (::T, T) where we didn't learn about the type until
                 * during the signature bind.) */
                if ((paramFlags & SIG_ELEM_TYPE_GENERIC) != 0) {
                    HOW = paramType.st.HOW;
                    SixModelObject ig = Ops.findmethod(HOW,
                        "instantiate_generic", tc);
                    ContextRef = tc.gc.ContextRef;
                    SixModelObject cc = ContextRef.st.REPR.allocate(tc, ContextRef.st);
                    ((ContextRefInstance)cc).context = cf;
                    Ops.invokeDirect(tc, ig, genIns,
                        new Object[] { HOW, paramType, cc });
                    paramType = Ops.result_o(tc.curFrame);
                }

                /* If the expected type is Positional, see if we need to do the
                 * positional bind failover. */
                if (paramType == gcx.Positional) {
                    if (Ops.istype_nd(arg_o, gcx.PositionalBindFailover, tc) != 0) {
                        SixModelObject ig = Ops.findmethod(arg_o, "cache", tc);
                        Ops.invokeDirect(tc, ig, Ops.invocantCallSite, new Object[] { arg_o });
                        arg_o = Ops.result_o(tc.curFrame);
                        decontValue = Ops.decont(arg_o, tc);
                    }
                    else if (Ops.istype_nd(decontValue, gcx.PositionalBindFailover, tc) != 0) {
                        SixModelObject ig = Ops.findmethod(decontValue, "cache", tc);
                        Ops.invokeDirect(tc, ig, Ops.invocantCallSite, new Object[] { decontValue });
                        decontValue = Ops.result_o(tc.curFrame);
                    }
                }

                /* If not, do the check. If the wanted nominal type is Mu, then
                 * anything goes.
                 * When binding a slurpy named hash while compiling the setting don't check for Associative.
                 */
                if (paramType != gcx.Mu && !(isSlurpyNamed && paramType == gcx.Associative) && Ops.istype_nd(decontValue, paramType, tc) == 0) {
                    /* Type check failed; produce error if needed. */

                    /* Try to figure out the most helpful name for the expected. */
                    SixModelObject expectedType = null;
                    SixModelObject postConstraints = param.get_attribute_boxed(tc, gcx.Parameter,
                            "$!post_constraints", HINT_post_constraints);
                    if (postConstraints != null) {
                        SixModelObject consType = postConstraints.at_pos_boxed(tc, 0);
                        expectedType = (Ops.istype(consType, gcx.Code, tc) != 0)
                            ? paramType.st.WHAT
                            : consType.st.WHAT;
                    }
                    else {
                        expectedType = paramType.st.WHAT;
                    }

                    if (error != null) {
                        SixModelObject thrower = RakOps.getThrower(tc, "X::TypeCheck::Binding::Parameter");
                        if (thrower != null) {
                            error[0] = thrower;
                            error[1] = bindParamThrower;
                            error[2] = new Object[] { decontValue, expectedType.st.WHAT,
                                varName, param, (long)0 };
                        }
                        else {
                            error[0] = String.format(
                                "Nominal type check failed for parameter '%s'",
                                varName);
                        }
                    }

                    /* Report junction failure mode if it's a junction. */
                    return juncOrFail(tc, gcx, decontValue);
                }

                /* Also enforce definedness check */
                if ( (paramFlags & SIG_ELEM_DEFINEDNES_CHECK) != 0) {

                    /* Don't check decontValue for concreteness though, but arg_o,
                       seeing as we don't have a isconcrete_nodecont */
                    Boolean shouldBeConcrete = (paramFlags & SIG_ELEM_DEFINED_ONLY) != 0 && Ops.isconcrete(arg_o, tc) != 1;
                    if (shouldBeConcrete || ((paramFlags & SIG_ELEM_UNDEFINED_ONLY) != 0 && Ops.isconcrete(arg_o, tc) == 1)) {
                        if (error != null) {
                            String typeName = Ops.typeName(param.get_attribute_boxed(tc,
                                gcx.Parameter, "$!type", HINT_type), tc);
                            String argName = Ops.typeName(arg_o, tc);
                            String methodName = cf.codeRef.name;
                            SixModelObject thrower = RakOps.getThrower(tc, "X::Parameter::InvalidConcreteness");
                            if (thrower != null) {
                                error[0] = thrower;
                                error[1] = bindConcreteThrower;
                                error[2] = new Object[] { typeName, argName, methodName,
                                    varName, (long)(shouldBeConcrete ? 1 : 0),
                                    (long)(paramFlags & SIG_ELEM_INVOCANT) };
                            }
                            else {
                                if (methodName == null || methodName.isEmpty())
                                    methodName = "<anon>";
                                error[0] = ((paramFlags & SIG_ELEM_INVOCANT) != 0)
                                    ? shouldBeConcrete
                                        ? String.format(
                                            "Invocant of method '%s' must be an object instance of type '%s', not a type object of type '%s'.  Did you forget a '.new'?",
                                            methodName, typeName, argName)
                                        : String.format(
                                            "Invocant of method '%s' must be a type object of type '%s', not an object instance of type '%s'.  Did you forget a 'multi'?",
                                            methodName, typeName, argName)
                                    : shouldBeConcrete
                                        ? String.format(
                                            "Parameter '%s' of routine '%s' must be an object instance of type '%s', not a type object of type '%s'.  Did you forget a '.new'?",
                                            varName, methodName, typeName, argName)
                                        : String.format(
                                            "Parameter '%s' of routine '%s' must be a type object of type '%s', not an object instance of type '%s'.  Did you forget a 'multi'?",
                                            varName, methodName, typeName, argName);
                            }
                        }
                        return juncOrFail(tc, gcx, decontValue);
                    }
                }
            }
        }

        /* Type captures. */
        SixModelObject typeCaps = param.get_attribute_boxed(tc, gcx.Parameter,
            "@!type_captures", HINT_type_captures);
        if (typeCaps != null)
            bindTypeCaptures(tc, typeCaps, cf, decontValue.st.WHAT);

        /* Do a coercion, if one is needed. */
        HOW = paramType.st.HOW;
        SixModelObject archetypesMeth = Ops.findmethod(HOW, "archetypes", tc);
        Ops.invokeDirect(tc, archetypesMeth, Ops.invocantCallSite, new Object[] { HOW });
        SixModelObject Archetypes = Ops.result_o(tc.curFrame);
        SixModelObject coerciveMeth = Ops.findmethodNonFatal(Archetypes, "coercive", tc);
        if (coerciveMeth != null) {
            Ops.invokeDirect(tc, coerciveMeth, Ops.invocantCallSite, new Object[] { Archetypes });
            if (Ops.istrue(Ops.result_o(tc.curFrame), tc) == 1) {
                /* Coercing natives not possible - nothing to call a method on. */
                if (flag != CallSiteDescriptor.ARG_OBJ) {
                    if (error != null)
                        error[0] = String.format(
                            "Unable to coerce natively typed parameter '%s'",
                            varName);
                    return BIND_RESULT_FAIL;
                }

                SixModelObject coerceMeth = Ops.findmethod(HOW, "coerce", tc);
                Ops.invokeDirect(tc, coerceMeth, genIns, new Object[] { HOW, paramType, arg_o });
                arg_o = Ops.result_o(tc.curFrame);
                decontValue = Ops.decont(arg_o, tc);
            }
        }

        /* If it's not got attributive binding, we'll go about binding it into the
         * lex pad. */
        StaticCodeInfo sci = cf.codeRef.staticInfo;
        if ((paramFlags & SIG_ELEM_BIND_ATTRIBUTIVE) == 0) {
            /* Is it native? If so, just go ahead and bind it. */
            if (flag != CallSiteDescriptor.ARG_OBJ) {
                if (hasVarName) {
                    switch (flag) {
                        case CallSiteDescriptor.ARG_INT:
                            cf.iLex[sci.iTryGetLexicalIdx(varName)] = arg_i;
                            break;
                        case CallSiteDescriptor.ARG_UINT:
                            cf.iLex[sci.uTryGetLexicalIdx(varName)] = arg_i;
                            break;
                        case CallSiteDescriptor.ARG_NUM:
                            cf.nLex[sci.nTryGetLexicalIdx(varName)] = arg_n;
                            break;
                        case CallSiteDescriptor.ARG_STR:
                            cf.sLex[sci.sTryGetLexicalIdx(varName)] = arg_s;
                            break;
                    }
                }
            }

            /* Otherwise it's some objecty case. */
            else if (is_rw) {
                if (Ops.isrwcont(arg_o, tc) == 1) {
                    if (hasVarName)
                        cf.oLex[sci.oTryGetLexicalIdx(varName)] = arg_o;
                } else {
                    SixModelObject thrower = RakOps.getThrower(tc, "X::Parameter::RW");
                    if (thrower == null) {
                        error[0] = "Parameter expected a writable container";
                    } else {
                        error[0] = thrower;
                        error[1] = paramReadWriteThrower;
                        error[2] = new Object[] { decontValue, varName};
                    }
                    return BIND_RESULT_FAIL;
                }

            }
            else if (hasVarName) {
                if ((paramFlags & SIG_ELEM_IS_RAW) != 0) {
                    /* Just bind the thing as is into the lexpad. */
                    cf.oLex[sci.oTryGetLexicalIdx(varName)] = didHLLTransform ? decontValue : arg_o;
                }
                else {
                    /* If it's an array, copy means make a new one and store,
                     * and a normal bind is a straightforward binding plus
                     * adding a constraint. */
                    if ((paramFlags & SIG_ELEM_ARRAY_SIGIL) != 0) {
                        SixModelObject bindee = decontValue;
                        if ((paramFlags & SIG_ELEM_IS_COPY) != 0) {
                            SixModelObject BOOTArray = tc.gc.BOOTArray;
                            bindee = gcx.Array.st.REPR.allocate(tc, gcx.Array.st);
                            bindee.bind_attribute_boxed(tc, gcx.List, "$!reified",
                                HINT_LIST_reified, BOOTArray.st.REPR.allocate(tc, BOOTArray.st));
                            RakOps.p6store(bindee, decontValue, tc);
                        }
                        cf.oLex[sci.oTryGetLexicalIdx(varName)] = bindee;
                    }

                    /* If it's a hash, similar approach to array. */
                    else if ((paramFlags & SIG_ELEM_HASH_SIGIL) != 0) {
                        SixModelObject bindee = decontValue;
                        if ((paramFlags & SIG_ELEM_IS_COPY) != 0) {
                            SixModelObject BOOTHash = tc.gc.BOOTHash;
                            bindee = gcx.Hash.st.REPR.allocate(tc, gcx.Hash.st);
                            bindee.bind_attribute_boxed(tc, gcx.Map, "$!storage",
                                HINT_ENUMMAP_storage, BOOTHash.st.REPR.allocate(tc, BOOTHash.st));
                            RakOps.p6store(bindee, decontValue, tc);
                        }
                        cf.oLex[sci.oTryGetLexicalIdx(varName)] = bindee;
                    }

                    /* If it's a scalar, we always need to wrap it into a new
                     * container and store it, for copy or ro case (the rw bit
                     * in the container descriptor takes care of the rest). */
                    else {
                        boolean wrap = (paramFlags & SIG_ELEM_IS_COPY) != 0;
                        if (!wrap && paramType != null && gcx.Iterable != null) {
                            wrap = Ops.istype(gcx.Iterable, paramType, tc) != 0
                                || Ops.istype(paramType, gcx.Iterable, tc) != 0;
                        }
                        if (wrap || varName.equals("$_")) {
                            STable stScalar = gcx.Scalar.st;
                            SixModelObject new_cont = stScalar.REPR.allocate(tc, stScalar);
                            SixModelObject desc = param.get_attribute_boxed(tc, gcx.Parameter,
                                "$!container_descriptor", HINT_container_descriptor);
                            new_cont.bind_attribute_boxed(tc, gcx.Scalar, "$!descriptor",
                                RakudoContainerSpec.HINT_descriptor, desc);
                            new_cont.bind_attribute_boxed(tc, gcx.Scalar, "$!value",
                                RakudoContainerSpec.HINT_value, decontValue);
                            cf.oLex[sci.oTryGetLexicalIdx(varName)] = new_cont;
                        }
                        else {
                            cf.oLex[sci.oTryGetLexicalIdx(varName)] = decontValue;
                        }
                    }
                }
            }
        }

        /* Is it the invocant? If so, also have to bind to self lexical. */
        if ((paramFlags & SIG_ELEM_INVOCANT) != 0)
            cf.oLex[sci.oTryGetLexicalIdx("self")] = decontValue;

        /* Handle any constraint types (note that they may refer to the parameter by
         * name, so we need to have bound it already). */
        SixModelObject postConstraints = param.get_attribute_boxed(tc, gcx.Parameter,
            "$!post_constraints", HINT_post_constraints);
        if (postConstraints != null) {
            long numConstraints = postConstraints.elems(tc);
            for (long i = 0; i < numConstraints; i++) {
                /* Check we meet the constraint. */
                SixModelObject consType = postConstraints.at_pos_boxed(tc, i);
                SixModelObject acceptsMeth = Ops.findmethod(consType, "ACCEPTS", tc);
                if (Ops.isconcrete(consType, tc) == 1 && Ops.istype(consType, gcx.Code, tc) != 0)
                    RakOps.p6capturelex(consType, tc);
                switch (flag) {
                    case CallSiteDescriptor.ARG_INT:
                        Ops.invokeDirect(tc, acceptsMeth,
                            ACCEPTS_i, new Object[] { consType, arg_i });
                        break;
                    case CallSiteDescriptor.ARG_UINT:
                        Ops.invokeDirect(tc, acceptsMeth,
                            ACCEPTS_u, new Object[] { consType, arg_i });
                        break;
                    case CallSiteDescriptor.ARG_NUM:
                        Ops.invokeDirect(tc, acceptsMeth,
                            ACCEPTS_n, new Object[] { consType, arg_n });
                        break;
                    case CallSiteDescriptor.ARG_STR:
                        Ops.invokeDirect(tc, acceptsMeth,
                            ACCEPTS_s, new Object[] { consType, arg_s });
                        break;
                    default:
                        Ops.invokeDirect(tc, acceptsMeth,
                            ACCEPTS_o, new Object[] { consType, arg_o });
                        break;
                }
                if (Ops.istrue(Ops.result_o(tc.curFrame), tc) == 0) {
                    /* Constraint type check failed; produce error if needed. */
                    if (error != null) {
                        SixModelObject thrower = RakOps.getThrower(tc, "X::TypeCheck::Binding::Parameter");
                        if (thrower != null) {
                            error[0] = thrower;
                            error[1] = bindParamThrower;
                            error[2] = new Object[] { (SixModelObject)origArg,
                                consType.st.WHAT, varName, param, (long)1 };
                        }
                        else {
                            error[0] = String.format(
                                "Constraint type check failed for parameter '%s'",
                                varName);
                        }
                    }
                    return BIND_RESULT_FAIL;
                }
            }
        }

        /* TODO: attributives. */
        if ((paramFlags & SIG_ELEM_BIND_ATTRIBUTIVE) != 0) {
            if (flag != CallSiteDescriptor.ARG_OBJ) {
                if (error != null)
                    error[0] = "Native attributive binding not yet implemented";
                return BIND_RESULT_FAIL;
            }
            int result = assignAttributive(tc, cf, varName, paramFlags,
                param.get_attribute_boxed(tc, gcx.Parameter, "$!attr_package", HINT_attr_package),
                decontValue, error);
            if (result != BIND_RESULT_OK)
                return result;
        }

        /* If it has a sub-signature, bind that. */
        SixModelObject subSignature = param.get_attribute_boxed(tc, gcx.Parameter,
            "$!sub_signature", HINT_sub_signature);
        if (subSignature != null && flag == CallSiteDescriptor.ARG_OBJ) {
            /* Turn value into a capture, unless we already have one. */
            SixModelObject capture = null;
            int result;
            if ((paramFlags & SIG_ELEM_IS_CAPTURE) != 0) {
                capture = decontValue;
            }
            else {
                SixModelObject meth = Ops.findmethodNonFatal(decontValue, "Capture", tc);
                if (meth == null) {
                    if (error != null)
                        error[0] = "Could not turn argument into capture";
                    return BIND_RESULT_FAIL;
                }
                Ops.invokeDirect(tc, meth, Ops.invocantCallSite, new Object[] { decontValue });
                capture = Ops.result_o(tc.curFrame);
            }

            SixModelObject subParams = subSignature
                .get_attribute_boxed(tc, gcx.Signature, "@!params", HINT_SIG_params);
            /* Recurse into signature binder. */
            CallSiteDescriptor subCsd = explodeCapture(tc, gcx, capture);
            result = bind(tc, gcx, cf, subParams, subCsd, tc.flatArgs, noNomTypeCheck, error);
            if (result != BIND_RESULT_OK)
            {
                if (error != null && error[0] instanceof String) {
                    /* Note in the error message that we're in a sub-signature. */
                    error[0] += " in sub-signature";

                    /* Have we a variable name? */
                    if (varName != null) {
                        error[0] += " of parameter " + varName;
                    }
                }
                return BIND_RESULT_FAIL;
            }
        }

        if (RakOps.DEBUG_MODE)
            System.err.println("bindOneParam NYFI");

        return BIND_RESULT_OK;
    }

    private static final CallSiteDescriptor exploder = new CallSiteDescriptor(new byte[] {
        CallSiteDescriptor.ARG_OBJ | CallSiteDescriptor.ARG_FLAT,
            CallSiteDescriptor.ARG_OBJ | CallSiteDescriptor.ARG_FLAT | CallSiteDescriptor.ARG_NAMED
    }, null);
    public static CallSiteDescriptor explodeCapture(ThreadContext tc, RakOps.GlobalExt gcx, SixModelObject capture) {
        capture = Ops.decont(capture, tc);

        SixModelObject capType = gcx.Capture;
        SixModelObject list = capture.get_attribute_boxed(tc, capType, "@!list", HINT_CAPTURE_list);
        SixModelObject hash = capture.get_attribute_boxed(tc, capType, "%!hash", HINT_CAPTURE_hash);
        if (list == null)
            list = gcx.EMPTYARR;
        if (hash == null)
            hash = gcx.EMPTYHASH;

        return exploder.explodeFlattening(tc.curFrame, new Object[] { list, hash });
    }

    private static final CallSiteDescriptor parameterizeArray = new CallSiteDescriptor(
        new byte[] { CallSiteDescriptor.ARG_OBJ, CallSiteDescriptor.ARG_OBJ, CallSiteDescriptor.ARG_OBJ }, null);
    private static final CallSiteDescriptor parameterizeHash = new CallSiteDescriptor(
        new byte[] { CallSiteDescriptor.ARG_OBJ, CallSiteDescriptor.ARG_OBJ,
            CallSiteDescriptor.ARG_OBJ, CallSiteDescriptor.ARG_OBJ
        }, null);
    /* This takes a signature element and either runs the closure to get a default
     * value if there is one, or creates an appropriate undefined-ish thingy. */
    private static SixModelObject handleOptional(ThreadContext tc, RakOps.GlobalExt gcx, int flags, SixModelObject param, CallFrame cf) {
        /* Is the "get default from outer" flag set? */
        if ((flags & SIG_ELEM_DEFAULT_FROM_OUTER) != 0) {
            param.get_attribute_native(tc, gcx.Parameter, "$!variable_name", HINT_variable_name);
            String varName = tc.native_s;
            CallFrame curOuter = cf.outer;
            while (curOuter != null) {
                int idx = curOuter.codeRef.staticInfo.oTryGetLexicalIdx(varName);
                if (idx != -1)
                    return curOuter.oLex[idx];
                curOuter = curOuter.outer;
            }
            return null;
        }

        /* Do we have a default value or value closure? */
        SixModelObject defaultValue = param.get_attribute_boxed(tc, gcx.Parameter,
            "$!default_value", HINT_default_value);
        if (defaultValue != null) {
            if ((flags & SIG_ELEM_DEFAULT_IS_LITERAL) != 0) {
                return defaultValue;
            }
            else {
                /* Thunk; run it to get a value. */
                Ops.invokeArgless(tc, defaultValue);
                return Ops.result_o(tc.curFrame);
            }
        }

        /* Otherwise, go by sigil to pick the correct default type of value. */
        else {
            if ((flags & SIG_ELEM_ARRAY_SIGIL) != 0) {
                SixModelObject paramType = param.get_attribute_boxed(tc, gcx.Parameter, "$!type", HINT_type);
                SixModelObject defaultType = null;

                if (paramType == gcx.Positional) {
                    defaultType = gcx.Array;
                }
                else {
                    /* TODO: Find clean solution for handling @deprecation
                     * during compiliation of setting. */
                    param.get_attribute_native(tc, gcx.Parameter, "$!variable_name", HINT_variable_name);
                    String varName = tc.native_s;
                    if (varName.equals("@deprecation")) {
                        SixModelObject compilingCoreSetting = Ops.getlexdyn("$*COMPILING_CORE_SETTING", tc);
                        if (Ops.isnull(compilingCoreSetting) == 0)
                            defaultType = gcx.Array;
                    }

                    if (defaultType == null) {
                        SixModelObject ofMeth = Ops.findmethod(paramType, "of", tc);
                        Ops.invokeDirect(tc, ofMeth, Ops.invocantCallSite, new Object[] { paramType });
                        SixModelObject ofType = Ops.result_o(tc.curFrame);

                        SixModelObject arrayHOW = gcx.Array.st.HOW;
                        SixModelObject parameterizeMeth = Ops.findmethod(arrayHOW, "parameterize", tc);
                        Ops.invokeDirect(tc, parameterizeMeth, parameterizeArray, new Object[] { arrayHOW, gcx.Array, ofType });
                        defaultType = Ops.result_o(tc.curFrame);
                    }
                }

                SixModelObject res = Ops.create(defaultType, tc);
                return res;
            }
            else if ((flags & SIG_ELEM_HASH_SIGIL) != 0) {
                SixModelObject paramType = param.get_attribute_boxed(tc, gcx.Parameter, "$!type", HINT_type);
                SixModelObject defaultType;

                if (paramType == gcx.Associative) {
                    defaultType = gcx.Hash;
                }
                else {
                    SixModelObject ofMeth = Ops.findmethod(paramType, "of", tc);
                    Ops.invokeDirect(tc, ofMeth, Ops.invocantCallSite, new Object[] { paramType });
                    SixModelObject ofType = Ops.result_o(tc.curFrame);

                    SixModelObject keyofMeth = Ops.findmethod(paramType, "keyof", tc);
                    Ops.invokeDirect(tc, keyofMeth, Ops.invocantCallSite, new Object[] { paramType });
                    SixModelObject keyofType = Ops.result_o(tc.curFrame);

                    SixModelObject hashHOW = gcx.Hash.st.HOW;
                    SixModelObject parameterizeMeth = Ops.findmethod(hashHOW, "parameterize", tc);
                    Ops.invokeDirect(tc, parameterizeMeth, parameterizeHash, new Object[] { hashHOW, gcx.Hash, ofType, keyofType });
                    defaultType = Ops.result_o(tc.curFrame);
                }

                SixModelObject res = Ops.create(defaultType, tc);
                return res;
            }
            else {
                param.get_attribute_native(tc, gcx.Parameter, "$!flags", HINT_flags);
                int paramFlags = (int)tc.native_i;
                switch (paramFlags & SIG_ELEM_NATIVE_VALUE) {
                    case SIG_ELEM_NATIVE_INT_VALUE:
                        return createBox(tc, gcx, (long)0, CallSiteDescriptor.ARG_INT);
                    case SIG_ELEM_NATIVE_UINT_VALUE:
                        return createBox(tc, gcx, (long)0, CallSiteDescriptor.ARG_UINT);
                    case SIG_ELEM_NATIVE_NUM_VALUE:
                        return createBox(tc, gcx, (double)0.0, CallSiteDescriptor.ARG_NUM);
                    case SIG_ELEM_NATIVE_STR_VALUE:
                        return createBox(tc, gcx, null, CallSiteDescriptor.ARG_STR);
                    default:
                        /* Do a coercion, if one is needed. */
                        SixModelObject paramType = param.get_attribute_boxed(tc, gcx.Parameter, "$!type", HINT_type);
                        SixModelObject HOW = paramType.st.HOW;
                        SixModelObject archetypesMeth = Ops.findmethod(HOW, "archetypes", tc);
                        Ops.invokeDirect(tc, archetypesMeth, Ops.invocantCallSite, new Object[] { HOW });
                        SixModelObject Archetypes = Ops.result_o(tc.curFrame);
                        SixModelObject coerciveMeth = Ops.findmethodNonFatal(Archetypes, "coercive", tc);
                        if (coerciveMeth != null) {
                            Ops.invokeDirect(tc, coerciveMeth, Ops.invocantCallSite, new Object[] { Archetypes });
                            if (Ops.istrue(Ops.result_o(tc.curFrame), tc) == 1) {
                                SixModelObject targetTypeMeth = Ops.findmethod(HOW, "target_type", tc);
                                Ops.invokeDirect(tc, targetTypeMeth, targetType, new Object[] { HOW, paramType });
                                return Ops.result_o(tc.curFrame);
                            }
                        }
                        return paramType;
                }
            }
        }
    }

    /* Takes a signature along with positional and named arguments and binds them
     * into the provided callframe. Returns BIND_RESULT_OK if binding works out,
     * BIND_RESULT_FAIL if there is a failure and BIND_RESULT_JUNCTION if the
     * failure was because of a Junction being passed (meaning we need to auto-thread). */
     private static final CallSiteDescriptor slurpyFromArgs = new CallSiteDescriptor(
        new byte[] { CallSiteDescriptor.ARG_OBJ, CallSiteDescriptor.ARG_OBJ }, null);
    public static int bind(ThreadContext tc, RakOps.GlobalExt gcx, CallFrame cf, SixModelObject params,
            CallSiteDescriptor csd, Object[] args,
            boolean noNomTypeCheck, Object[] error) {
        int bindFail = BIND_RESULT_OK;
        int curPosArg = 0;

        /* If we have a |$foo that's followed by slurpies, then we can suppress
         * any future arity checks. */
        boolean suppressArityFail = false;

        /* If we do have some named args, we want to make a clone of the hash
         * to work on. We'll delete stuff from it as we bind, and what we have
         * left over can become the slurpy hash or - if we aren't meant to be
         * taking one - tell us we have a problem. */
        Object2IntOpenHashMap<String> namedArgsCopy = csd.nameMap == null
            ? null
            : new Object2IntOpenHashMap<String>(csd.nameMap);

        /* Now we'll walk through the signature and go about binding things. */
        int numPosArgs = csd.numPositionals;
        long numParams = params.elems(tc);
        for (long i = 0; i < numParams; i++) {
            /* Get parameter, its flags and any named names. */
            SixModelObject param = params.at_pos_boxed(tc, i);
            param.get_attribute_native(tc, gcx.Parameter, "$!flags", HINT_flags);
            int flags = (int)tc.native_i;
            SixModelObject namedNames = param.get_attribute_boxed(tc,
                gcx.Parameter, "@!named_names", HINT_named_names);

            /* Is it looking for us to bind a capture here? */
            if ((flags & SIG_ELEM_IS_CAPTURE) != 0) {
                /* Capture the arguments from this point forwards into a Capture.
                 * Of course, if there's no variable name we can (cheaply) do pretty
                 * much nothing. */
                param.get_attribute_native(tc, gcx.Parameter, "$!variable_name", HINT_variable_name);
                if (tc.native_s == null) {
                   bindFail = BIND_RESULT_OK;
                }
                else {
                    SixModelObject posArgs = gcx.EMPTYARR.clone(tc);
                    for (int k = curPosArg; k < numPosArgs; k++) {
                        switch (csd.argFlags[k]) {
                        case CallSiteDescriptor.ARG_OBJ:
                            posArgs.push_boxed(tc, (SixModelObject)args[k]);
                            break;
                        case CallSiteDescriptor.ARG_INT:
                            posArgs.push_boxed(tc, RakOps.p6box_i((long)args[k], tc));
                            break;
                        case CallSiteDescriptor.ARG_UINT:
                            posArgs.push_boxed(tc, RakOps.p6box_u((long)args[k], tc));
                            break;
                        case CallSiteDescriptor.ARG_NUM:
                            posArgs.push_boxed(tc, RakOps.p6box_n((double)args[k], tc));
                            break;
                        case CallSiteDescriptor.ARG_STR:
                            posArgs.push_boxed(tc, RakOps.p6box_s((String)args[k], tc));
                            break;
                        }
                    }
                    SixModelObject namedArgs = vmHashOfRemainingNameds(tc, gcx, namedArgsCopy, args);

                    SixModelObject capType = gcx.Capture;
                    SixModelObject capSnap = capType.st.REPR.allocate(tc, capType.st);
                    capSnap.bind_attribute_boxed(tc, capType, "@!list", HINT_CAPTURE_list, posArgs);
                    capSnap.bind_attribute_boxed(tc, capType, "%!hash", HINT_CAPTURE_hash, namedArgs);

                    bindFail = bindOneParam(tc, gcx, cf, param, capSnap, CallSiteDescriptor.ARG_OBJ,
                        noNomTypeCheck, false, error);
                }
                if (bindFail != 0) {
                    return bindFail;
                }
                else if (i + 1 == numParams) {
                    /* Since a capture acts as "the ultimate slurpy" in a sense, if
                     * this is the last parameter in the signature we can return
                     * success right off the bat. */
                    return BIND_RESULT_OK;
                }
                else {
                    SixModelObject nextParam = params.at_pos_boxed(tc, i + 1);
                    nextParam.get_attribute_native(tc, gcx.Parameter, "$!flags", HINT_flags);
                    if (((int)tc.native_i & (SIG_ELEM_SLURPY_POS | SIG_ELEM_SLURPY_NAMED)) != 0)
                        suppressArityFail = true;
                }
            }

            /* Could it be a named slurpy? */
            else if ((flags & SIG_ELEM_SLURPY_NAMED) != 0) {
                SixModelObject slurpy = vmHashOfRemainingNameds(tc, gcx, namedArgsCopy, args);
                SixModelObject bindee = gcx.Hash.st.REPR.allocate(tc, gcx.Hash.st);
                bindee.bind_attribute_boxed(tc, gcx.Map, "$!storage",
                    HINT_ENUMMAP_storage, slurpy);
                bindFail = bindOneParam(tc, gcx, cf, param, bindee, CallSiteDescriptor.ARG_OBJ,
                    noNomTypeCheck, true, error);
                if (bindFail != 0)
                    return bindFail;

                /* Nullify named arguments hash now we've consumed it, to mark all
                 * is well. */
                namedArgsCopy = null;
            }

            /* Otherwise, maybe it's a positional of some kind. */
            else if (namedNames == null) {
                /* Slurpy or LoL-slurpy? */
                if ((flags & (SIG_ELEM_SLURPY_POS | SIG_ELEM_SLURPY_LOL | SIG_ELEM_SLURPY_ONEARG)) != 0) {
                    /* Create Raku array, create VM array of all remaining things,
                     * then store it. */
                    SixModelObject slurpy = gcx.EMPTYARR.clone(tc);
                    while (curPosArg < numPosArgs) {
                        switch (csd.argFlags[curPosArg]) {
                        case CallSiteDescriptor.ARG_OBJ:
                            slurpy.push_boxed(tc, (SixModelObject)args[curPosArg]);
                            break;
                        case CallSiteDescriptor.ARG_INT:
                            slurpy.push_boxed(tc, RakOps.p6box_i((long)args[curPosArg], tc));
                            break;
                        case CallSiteDescriptor.ARG_UINT:
                            slurpy.push_boxed(tc, RakOps.p6box_u((long)args[curPosArg], tc));
                            break;
                        case CallSiteDescriptor.ARG_NUM:
                            slurpy.push_boxed(tc, RakOps.p6box_n((double)args[curPosArg], tc));
                            break;
                        case CallSiteDescriptor.ARG_STR:
                            slurpy.push_boxed(tc, RakOps.p6box_s((String)args[curPosArg], tc));
                            break;
                        }
                        curPosArg++;
                    }

                    SixModelObject slurpyType = (flags & SIG_ELEM_IS_RAW) != 0 ? gcx.List : gcx.Array;
                    SixModelObject sm = Ops.findmethod(slurpyType,
                        (flags & SIG_ELEM_SLURPY_ONEARG) != 0 ? "from-slurpy-onearg" :
                        (flags & SIG_ELEM_SLURPY_POS) != 0 ? "from-slurpy-flat" : "from-slurpy",
                        tc);
                    Ops.invokeDirect(tc, sm, slurpyFromArgs, new Object[] { slurpyType, slurpy });
                    SixModelObject bindee = Ops.result_o(tc.curFrame);

                    bindFail = bindOneParam(tc, gcx, cf, param, bindee, CallSiteDescriptor.ARG_OBJ,
                        noNomTypeCheck, false, error);
                    if (bindFail != 0)
                        return bindFail;
                }

                /* Otherwise, a positional. */
                else {
                    /* Do we have a value? */
                    if (curPosArg < numPosArgs) {
                        /* Easy - just bind that. */
                        bindFail = bindOneParam(tc, gcx, cf, param, args[curPosArg],
                            csd.argFlags[curPosArg], noNomTypeCheck, false, error);
                        if (bindFail != 0)
                            return bindFail;
                        curPosArg++;
                    }
                    else {
                        /* No value. If it's optional, fetch a default and bind that;
                         * if not, we're screwed. Note that we never nominal type check
                         * an optional with no value passed. */
                        if ((flags & SIG_ELEM_IS_OPTIONAL) != 0) {
                            bindFail = bindOneParam(tc, gcx, cf, param,
                                handleOptional(tc, gcx, flags, param, cf),
                                CallSiteDescriptor.ARG_OBJ, false, false, error);
                            if (bindFail != 0)
                                return bindFail;
                        }
                        else {
                            if (error != null)
                                error[0] = arityFail(tc, gcx, cf, params, (int)numParams, numPosArgs, false);
                            return BIND_RESULT_FAIL;
                        }
                    }
                }
            }

            /* Else, it's a non-slurpy named. */
            else {
                /* Try and get hold of value. */
                int lookup = -1;
                if (namedArgsCopy != null) {
                    long numNames = namedNames.elems(tc);
                    for (long j = 0; j < numNames; j++) {
                        namedNames.at_pos_native(tc, j);
                        String name = tc.native_s;
                        if (namedArgsCopy.containsKey(name)) {
                            lookup = namedArgsCopy.remove(name);
                            break;
                        }
                    }
                }

                /* Did we get one? */
                if (lookup == -1) {
                    /* Nope. We'd better hope this param was optional... */
                    if ((flags & SIG_ELEM_IS_OPTIONAL) != 0) {
                        bindFail = bindOneParam(tc, gcx, cf, param,
                            handleOptional(tc, gcx, flags, param, cf),
                            CallSiteDescriptor.ARG_OBJ, false, false, error);
                    }
                    else if (!suppressArityFail) {
                        if (error != null) {
                            namedNames.at_pos_native(tc, 0);
                            error[0] = "Required named argument '" +
                                tc.native_s +
                                "' not passed";
                        }
                        return BIND_RESULT_FAIL;
                    }
                }
                else {
                    bindFail = bindOneParam(tc, gcx, cf, param, args[lookup >> 6],
                        (byte)(lookup & 7), noNomTypeCheck, false, error);
                }

                /* If we got a binding failure, return it. */
                if (bindFail != 0)
                    return bindFail;
            }
        }

        /* Do we have any left-over args? */
        if (curPosArg < numPosArgs && !suppressArityFail) {
            /* Oh noes, too many positionals passed. */
            if (error != null)
                error[0] = arityFail(tc, gcx, cf, params, (int)numParams, numPosArgs, true);
            return BIND_RESULT_FAIL;
        }
        if (namedArgsCopy != null && namedArgsCopy.size() > 0) {
            /* Oh noes, unexpected named args. */
            if (error != null) {
                int numExtra = namedArgsCopy.size();
                if (numExtra == 1) {
                    for (String name : namedArgsCopy.keySet()) {
                        error[0] = "Unexpected named argument '" + name + "' passed";
                    }
                }
                else {
                    boolean first = true;
                    error[0] = numExtra + " unexpected named arguments passed (";
                    for (String name : namedArgsCopy.keySet()) {
                        if (!first)
                            error[0] += ", ";
                        else
                            first = false;
                        error[0] += name;
                    }
                    error[0] += ")";
                }
            }
            return BIND_RESULT_FAIL;
        }

        /* If we get here, we're done. */
        return BIND_RESULT_OK;
    }

    /* Takes any nameds we didn't capture yet and makes a VM Hash of them. */
    private static SixModelObject vmHashOfRemainingNameds(ThreadContext tc, RakOps.GlobalExt gcx, Object2IntOpenHashMap<String> namedArgsCopy, Object[] args) {
        SixModelObject slurpy = gcx.Mu;
        if (namedArgsCopy != null) {
            SixModelObject BOOTHash = tc.gc.BOOTHash;
            slurpy = BOOTHash.st.REPR.allocate(tc, BOOTHash.st);
            for (String name : namedArgsCopy.keySet()) {
                int lookup = namedArgsCopy.getInt(name);
                switch (lookup & 7) {
                case CallSiteDescriptor.ARG_OBJ:
                    slurpy.bind_key_boxed(tc, name, (SixModelObject)args[lookup >> 6]);
                    break;
                case CallSiteDescriptor.ARG_INT:
                    slurpy.bind_key_boxed(tc, name, RakOps.p6box_i((long)args[lookup >> 6], tc));
                    break;
                case CallSiteDescriptor.ARG_UINT:
                    slurpy.bind_key_boxed(tc, name, RakOps.p6box_u((long)args[lookup >> 6], tc));
                    break;
                case CallSiteDescriptor.ARG_NUM:
                    slurpy.bind_key_boxed(tc, name, RakOps.p6box_n((double)args[lookup >> 6], tc));
                    break;
                case CallSiteDescriptor.ARG_STR:
                    slurpy.bind_key_boxed(tc, name, RakOps.p6box_s((String)args[lookup >> 6], tc));
                    break;
                }
            }
        }
        return slurpy;
    }
}
