AbstractStructTupleCompiler.java

package com.mackenziehigh.autumn.lang.compiler.compilers;

import autumn.lang.compiler.ClassFile;
import autumn.lang.compiler.ast.commons.IRecord;
import autumn.lang.internals.Helpers;
import com.google.common.collect.Lists;
import com.mackenziehigh.autumn.lang.compiler.typesystem.CustomConstructor;
import com.mackenziehigh.autumn.lang.compiler.typesystem.CustomFormalParameter;
import com.mackenziehigh.autumn.lang.compiler.typesystem.CustomMethod;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IConstructor;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IFormalParameter;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IInterfaceType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IMethod;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IVariableType;
import com.mackenziehigh.autumn.lang.compiler.utils.BridgeMethod;
import com.mackenziehigh.autumn.lang.compiler.utils.GetterMethod;
import com.mackenziehigh.autumn.lang.compiler.utils.RecordElement;
import com.mackenziehigh.autumn.lang.compiler.utils.SetterMethod;
import com.mackenziehigh.autumn.lang.compiler.utils.TypeSystemUtils;
import com.mackenziehigh.autumn.lang.compiler.utils.Utils;
import java.util.LinkedList;
import java.util.List;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TableSwitchInsnNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;

/**
 * This class generalizes the compilation of struct-definitions and tuple-definitions.
 *
 * <p>
 * Note: Design-definitions are technically records, but they are compiled very differently.
 * </p>
 *
 * @author Mackenzie High
 */
class AbstractStructTupleCompiler
        extends AbstractRecordCompiler
{
    /**
     * This is the type of the tuple's only constructor.
     */
    private CustomConstructor ctor;

    /**
     * This flag is true, iff the record is a struct.
     */
    private final boolean struct;

    /**
     * Sole Constructor.
     *
     * @param module is the module that contains the tuple being compiled.
     * @param node is the AST node that represents the tuple being compiled.
     */
    public AbstractStructTupleCompiler(final ModuleCompiler module,
                                       final IRecord node,
                                       final boolean is_tuple)
    {
        super(module, node);

        assert module != null;
        assert node != null;

        this.struct = !is_tuple;
    }

    /**
     * This method gets the name of a field that stores an element.
     *
     * <p>
     * This method is needed in order to prevent name collisions with special fields.
     * </p>
     *
     * @param element is the name of the element.
     * @return the name of the field that stores the value of the element.
     */
    private String nameOfField(final String element)
    {
        assert element != null;

        return "element$" + element;
    }

    /**
     * This method generates the compiled class-file.
     *
     * @return the compiled class-file.
     */
    public ClassFile build()
    {
        final String internal_name = Utils.internalName(type);

        final String source_name = Utils.sourceName(type);

        /**
         * Create the bytecode representations of the tuple's fields.
         */
        final List<FieldNode> fields = Lists.newLinkedList();

        // Create the field that stores the names of the elements.
        fields.add(new FieldNode(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL,
                                 "KEYS",
                                 "Ljava/util/List;",
                                 null,
                                 null));

        // Create the field that stores the types of the elements.
        fields.add(new FieldNode(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL,
                                 "TYPES",
                                 "Ljava/util/List;",
                                 null,
                                 null));

        // Create the field that stores the instance() tuple.
        fields.add(new FieldNode(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL,
                                 "INSTANCE",
                                 type.getDescriptor(),
                                 null,
                                 null));

        // Create the fields that store the values of the elements.
        fields.addAll(this.generateElementFields());

        /**
         * Create the bytecode representation of the tuple itself.
         */
        final ClassNode clazz = new ClassNode();
        {
            clazz.version = Opcodes.V1_6;
            clazz.visibleAnnotations = module.anno_utils.compileAnnotationList(type.getAnnotations());
            clazz.access = type.getModifiers();
            clazz.name = internal_name;
            clazz.superName = Utils.internalName(type.getSuperclass());
            clazz.fields = fields;
            clazz.methods = Lists.newLinkedList();
            clazz.sourceFile = String.valueOf(node.getLocation().getFile());

            /**
             * The record may implement zero or more designs, as specified by the user.
             */
            for (IInterfaceType superinterface : type.getSuperinterfaces())
            {
                clazz.interfaces.add(Utils.internalName(superinterface));
            }

            /**
             * Add some special methods to the tuple.
             */
            clazz.methods.add(this.generateStaticCtor());

            clazz.methods.add(this.generateCtor());

            clazz.methods.add(this.generateMethodInstance());

            clazz.methods.add(this.generateMethodIsStruct());

            clazz.methods.add(this.generateMethodIsTuple());

            clazz.methods.add(this.generateMethodKeys());

            clazz.methods.add(this.generateMethodTypes());

            clazz.methods.add(this.generateMethodGet());

            clazz.methods.add(this.generateMethodSet());

            clazz.methods.addAll(this.generateBridgeMethods());

            /**
             * Generate the setter and getter methods.
             */
            for (RecordElement element : analyzer.elements.values())
            {
                /**
                 * Generate the primary setter and primary getter methods.
                 * In other words, generate the methods that can actually set or get an element.
                 */
                clazz.methods.add(this.generateSetter(element.setter()));
                clazz.methods.add(this.generateGetter(element.getter()));
            }
        }

//        for (IMethod m : type.getAllVisibleMethods())
//        {
//            System.out.println(m.getNamePlusDescriptor() + " in " + m.getOwner().getDescriptor());
//        }

        /**
         * Assemble the bytecode into an array of bytes.
         */
        final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        clazz.accept(writer);
        final byte[] bytecode = writer.toByteArray();

        /**
         * Create the class-file object that will store the emitted bytecode.
         */
        final ClassFile file = new ClassFile(source_name, bytecode);

        return file;
    }

    /**
     * This method creates the type-system representation of the only constructor in the tuple.
     *
     * @return the aforedescribed constructor.
     */
    @Override
    protected IConstructor typeofCtor()
    {
        final List<IFormalParameter> formals = Lists.newLinkedList();

        /**
         * Create a formal parameter for each element in the tuple.
         */
        for (String element : keys)
        {
            final CustomFormalParameter formal = new CustomFormalParameter();

            formal.setType(typeOfElement(element));

            formals.add(formal);
        }

        ctor = new CustomConstructor(program.typesystem.typefactory());

        ctor.setAnnotations(new LinkedList());
        ctor.setModifiers(Opcodes.ACC_PUBLIC);
        ctor.setOwner(type);
        ctor.setParameters(formals);
        ctor.setReturnType(program.typesystem.utils.VOID);
        ctor.setThrowsClause(new LinkedList());

        return ctor;
    }

    /**
     * This method creates the type-system representation of the instance() method.
     *
     * @return the aforedescribed method.
     */
    @Override
    protected IMethod typeofInstance()
    {
        final CustomMethod method = new CustomMethod(program.typesystem.typefactory(), false);

        method.setAnnotations(new LinkedList());
        method.setModifiers(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC);
        method.setName("instance");
        method.setOwner(type);
        method.setParameters(new LinkedList());
        method.setReturnType(type);
        method.setThrowsClause(new LinkedList());

        return method;
    }

    /**
     * This method generates the bytecode representations of the fields that store the elements.
     *
     * @return the generated bytecode.
     */
    private List<FieldNode> generateElementFields()
    {
        final List<FieldNode> fields = Lists.newLinkedList();

        /**
         * Generate one field for each element.
         */
        for (String element : keys)
        {
            // The field is private, because element's are accessed via getters and setters.
            final int access = Opcodes.ACC_PRIVATE;

            // The name of the field will be given a prefix.
            // This prevents name collisions with special fields.
            final String name = nameOfField(element);

            // The descriptor describes the static-type of the element.
            final String desc = typeOfElement(element).getDescriptor();

            // Create the bytecode representation of the field.
            final FieldNode field = new FieldNode(access, name, desc, null, null);

            fields.add(field);
        }

        return fields;
    }

    /**
     * This method generates the bytecode representation of the tuple's only constructor.
     *
     * <p>
     * The constructor's actual parameters are the values of the tuple's elements.
     * Parameter[i] of the constructor provides the value of element[i] of the tuple.
     * </p>
     *
     * @return the aforedescribed bytecode.
     */
    private MethodNode generateCtor()
    {
        // The generated constructor must do the following:
        // . invoke super()
        // . for each parameter [p]:
        // . . transfer [p] into the field that will store [p].
        // . return
        //
        //////////////////////////////////////////////////////////

        final MethodNode method = Utils.bytecodeOf(module, ctor);

        /**
         * Generate the bytecode that invokes super().
         */
        method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // Load 'this'
        method.instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                                   Utils.internalName(program.typesystem.utils.ABSTRACT_RECORD),
                                                   "<init>",
                                                   "()V"));


        /**
         * Skip the first memory address (i.e. address zero),
         * because that is where 'this' is automatically stored.
         */
        int address = 1;

        /**
         * Transfer each actual parameter into the field that stores the related element.
         */
        for (String element : keys)
        {
            final IVariableType element_type = typeOfElement(element);

            final String owner = Utils.internalName(type);
            final String name = nameOfField(element);
            final String desc = element_type.getDescriptor();

            method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // Load 'this'
            method.instructions.add(Utils.selectLoadVarInsn(element_type, address));
            method.instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, owner, name, desc));

            address += Utils.sizeof(element_type);
        }

        /**
         * Return from the constructor.
         */
        method.instructions.add(new InsnNode(Opcodes.RETURN));

        return method;
    }

    /**
     * This method generates the tuple's static constructor.
     *
     * <p>
     * The static-constructor is needed in order to initialize a few static fields.
     * </p>
     *
     * @return the generated method.
     */
    private MethodNode generateStaticCtor()
    {
        final MethodNode clinit = new MethodNode(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC,
                                                 "<clinit>",
                                                 "()V",
                                                 null,
                                                 new String[0]);

        /**
         * Initialize the KEYS field.
         */
        initListOfKeys(clinit.instructions);

        /**
         * Initialize the TYPES field.
         */
        initListOfTypes(clinit.instructions);

        /**
         * Initialize the INSTANCE field.
         */
        initInstance(clinit.instructions);

        /**
         * Exit the static constructor.
         */
        clinit.instructions.add(new InsnNode(Opcodes.RETURN));

        return clinit;
    }

    /**
     * This method generates bytecode that initializes the field that stores the list of keys.
     *
     * @param code is the bytecode being generated.
     */
    private void initListOfKeys(final InsnList code)
    {
        final CollectionCompiler<String> cmp = new CollectionCompiler<String>()
        {
            @Override
            public void compile(final String element)
            {
                code().add(new LdcInsnNode(element));
            }

            @Override
            public InsnList code()
            {
                return code;
            }
        };

        /**
         * Generate the bytecode that creates a mutable list containing the keys.
         */
        cmp.compile(keys);

        /**
         * Make the list immutable.
         */
        Utils.makeListImmutable(code);

        /**
         * Assign the immutable list to the field.
         */
        code.add(new FieldInsnNode(Opcodes.PUTSTATIC,
                                   Utils.internalName(type),
                                   "KEYS",
                                   "Ljava/util/List;"));
    }

    /**
     * This method generates bytecode that initializes the field that stores the map of types.
     *
     * @param code is the bytecode being generated.
     */
    private void initListOfTypes(final InsnList code)
    {
        final CollectionCompiler<String> cmp = new CollectionCompiler<String>()
        {
            @Override
            public void compile(final String element)
            {
                code().add(Utils.ldcClass(typeOfElement(element)));
            }

            @Override
            public InsnList code()
            {
                return code;
            }
        };

        /**
         * Generate the bytecode that creates a mutable list containing the keys.
         */
        cmp.compile(keys);

        /**
         * Make the list immutable.
         */
        Utils.makeListImmutable(code);

        /**
         * Assign the immutable list to the field.
         */
        code.add(new FieldInsnNode(Opcodes.PUTSTATIC,
                                   Utils.internalName(type),
                                   "TYPES",
                                   "Ljava/util/List;"));
    }

    /**
     * This method generates bytecode that initializes the field containing instance() tuple.
     *
     * @param code is the bytecode being generated.
     */
    private void initInstance(final InsnList code)
    {
        /**
         * Create a new uninitialized instance of the tuple.
         */
        code.add(new TypeInsnNode(Opcodes.NEW, Utils.internalName(type)));

        /**
         * Duplicate the object-reference.
         */
        code.add(new InsnNode(Opcodes.DUP));

        /**
         * Load the value of each element onto the operand-stack.
         */
        for (String element : keys)
        {
            /**
             * Load the default value of the element onto the operand-stack.
             */
            code.add(Utils.ldcDefault(typeOfElement(element)));
        }

        /**
         * Invoke the constructor in order to create a copy of this object.
         */
        code.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                    Utils.internalName(type),
                                    "<init>",
                                    ctor.getDescriptor()));

        /**
         * Assign the immutable list to the field.
         */
        code.add(new FieldInsnNode(Opcodes.PUTSTATIC,
                                   Utils.internalName(type),
                                   "INSTANCE",
                                   type.getDescriptor()));
    }

    /**
     * This method generates the bytecode representations of the bridge methods.
     *
     * @return the generated methods.
     */
    private List<MethodNode> generateBridgeMethods()
    {
        final List<MethodNode> result = Lists.newLinkedList();

        /**
         * Generate the bridge methods related to predefined methods.
         */
        for (BridgeMethod bridge : special_bridges)
        {
            result.add(bridge.compile(module));
        }

        /**
         * Generate the bridge methods related to elements.
         */
        for (RecordElement element : analyzer.elements().values())
        {
            /**
             * Generate the bridge getter methods.
             */
            for (GetterMethod getter : element.bridgeGetters())
            {
                final BridgeMethod bridge = new BridgeMethod(getter.findSelf(),
                                                             getter.findBridgeTarget());

                result.add(bridge.compile(module));
            }

            /**
             * Generate the bridge setter methods.
             */
            for (SetterMethod setter : element.bridgeSetters())
            {
                final BridgeMethod bridge = new BridgeMethod(setter.findSelf(),
                                                             setter.findBridgeTarget());

                result.add(bridge.compile(module));
            }
        }

        return result;
    }

    /**
     * This method generates the bytecode representation of the instance() method.
     *
     * @return the generated method.
     */
    private MethodNode generateMethodInstance()
    {
        final MethodNode method = Utils.bytecodeOf(module,
                                                   TypeSystemUtils.find(type.getMethods(),
                                                                        "instance",
                                                                        "()" + type.getDescriptor()));

        // Remove the abstract modifier.
        method.access = method.access & (~Opcodes.ACC_ABSTRACT);

        /**
         * The method simply retrieves the value in a static field and returns it.
         */
        method.instructions.add(new FieldInsnNode(Opcodes.GETSTATIC,
                                                  Utils.internalName(type),
                                                  "INSTANCE",
                                                  type.getDescriptor()));

        method.instructions.add(new InsnNode(Opcodes.ARETURN));

        return method;
    }

    /**
     * This method generates the bytecode representation of the isStruct() method.
     *
     * @return the generated method.
     */
    private MethodNode generateMethodIsStruct()
    {
        final MethodNode method = Utils.bytecodeOf(module,
                                                   TypeSystemUtils.find(type.getAllVisibleMethods(),
                                                                        "isStruct",
                                                                        "()Z"));

        // Remove the abstract modifier.
        method.access = method.access & (~Opcodes.ACC_ABSTRACT);

        // Return a constant boolean value.
        method.instructions.add(new LdcInsnNode(struct));
        method.instructions.add(new InsnNode(Opcodes.IRETURN));

        return method;
    }

    /**
     * This method generates the bytecode representation of the isTuple() method.
     *
     * @return the generated method.
     */
    private MethodNode generateMethodIsTuple()
    {
        final MethodNode method = Utils.bytecodeOf(module,
                                                   TypeSystemUtils.find(type.getAllVisibleMethods(),
                                                                        "isTuple",
                                                                        "()Z"));

        // Remove the abstract modifier.
        method.access = method.access & (~Opcodes.ACC_ABSTRACT);

        // Return a constant boolean value.
        method.instructions.add(new LdcInsnNode(!struct));
        method.instructions.add(new InsnNode(Opcodes.IRETURN));

        return method;
    }

    /**
     * This method generates the bytecode representation of the set(int, Object) method.
     *
     * @return the generated method.
     */
    private MethodNode generateMethodSet()
    {
        final MethodNode method = Utils.bytecodeOf(module,
                                                   TypeSystemUtils.find(type.getAllVisibleMethods(),
                                                                        "set",
                                                                        "(ILjava/lang/Object;)" + program.typesystem.utils.RECORD.getDescriptor()));

        // Remove the abstract modifier.
        method.access = method.access & (~Opcodes.ACC_ABSTRACT);

        final LabelNode default_case = new LabelNode();

        /**
         * Essentially, this will generate a switch-statement.
         * The switch-case branches based on the index that is given as an parameter to the method.
         * The default-case will be generated at the bottom of this method,.
         * because no jump-table is needed, if the tuple is empty.
         */
        if (!keys.isEmpty())
        {
            /**
             * Generate the jump-table itself.
             */
            final int min = 0;
            final int max = keys.isEmpty() ? 0 : keys.size() - 1;
            final TableSwitchInsnNode table = new TableSwitchInsnNode(min, max, default_case);

            // Load the actual argument onto the operand-stack and then branch.
            method.instructions.add(new VarInsnNode(Opcodes.ILOAD, 1));
            method.instructions.add(table);

            /**
             * Generate each case in the switch-case, except the default-case.
             */
            for (String element : keys)
            {
                /**
                 * Get the static-type of the element.
                 */
                final IVariableType element_type = typeOfElement(element);

                /**
                 * Mark the entry-point of the switch-case.
                 */
                final LabelNode label = new LabelNode();
                table.labels.add(label);
                method.instructions.add(label);

                /**
                 * Obtain a modifiable version of the tuple.
                 * If the tuple is modifiable, then this will be the tuple itself.
                 * Otherwise, this will be a copy of the tuple.
                 */
                loadModifiableVariant(method.instructions);

                /**
                 * Duplicate the object-reference, because we will need an extra one later.
                 */
                method.instructions.add(new InsnNode(Opcodes.DUP));

                /**
                 * Push the value onto an argument-stack and then pop it right back off.
                 * This will convert the value to the appropriate type.
                 *
                 * The argument is an object and is the second user-defined parameter.
                 * The first parameter is the index, which is an int.
                 */
                // Load two references to the the argument-stack.
                Utils.loadArgumentStack(method.instructions);
                method.instructions.add(new InsnNode(Opcodes.DUP));
                // Push
                method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 2));
                Utils.pushArgument(program, method.instructions, program.typesystem.utils.OBJECT);
                // Pop - Peek Value
                Utils.peekArgument(program, method.instructions, element_type);
                // Pop - Clear the Argument Stack
                Utils.loadArgumentStack(method.instructions);
                method.instructions.add(new InsnNode(Opcodes.POP));

                /**
                 * Assign the value to the element.
                 */
                method.instructions.add(new FieldInsnNode(Opcodes.PUTFIELD,
                                                          Utils.internalName(type),
                                                          nameOfField(element),
                                                          element_type.getDescriptor()));

                /**
                 * Return the modified object.
                 * This is why the object-reference was duplicated previously.
                 */
                method.instructions.add(new InsnNode(Opcodes.ARETURN));
            }

            /**
             * The default-case executes the same code that executes when the tuple is empty.
             */
            method.instructions.add(default_case);
        }

        /**
         * Throw an exception, if the element does not exist.
         */
        method.instructions.add(new VarInsnNode(Opcodes.ILOAD, 1));
        method.instructions.add(new LdcInsnNode(keys.size()));
        Utils.invoke(method.instructions,
                     Opcodes.INVOKESTATIC,
                     Helpers.class,
                     void.class,
                     "throwIndexOutOfBoundsException",
                     int.class,
                     int.class);

        /**
         * Add a return instruction, even though it will never actually execute.
         * This is needed, due to bytecode verification.
         */
        method.instructions.add(new InsnNode(Opcodes.ACONST_NULL));
        method.instructions.add(new InsnNode(Opcodes.ARETURN));

        return method;
    }

    /**
     * This method generates the bytecode representation of the get(int) method.
     *
     * @return the generated method.
     */
    private MethodNode generateMethodGet()
    {
        final MethodNode method = Utils.bytecodeOf(module,
                                                   TypeSystemUtils.find(type.getAllVisibleMethods(),
                                                                        "get",
                                                                        "(I)Ljava/lang/Object;"));

        // Remove the abstract modifier.
        method.access = method.access & (~Opcodes.ACC_ABSTRACT);

        final LabelNode default_case = new LabelNode();

        /**
         * Essentially, this will generate a switch-statement.
         * The switch-case branches based on the index that is given as an parameter to the method.
         * If a case is executed, it will do the following things.
         * First, the value of the element is loaded onto the operand-stack.
         * Second, the value will be auto-boxed, if the element is a primitive-type.
         * Third, the value will be returned from the method.
         * The default-case will be generated at the bottom of this method,.
         * because no jump-table is needed, if the tuple is empty.
         */
        if (!keys.isEmpty())
        {
            /**
             * Generate the jump-table itself.
             */
            final int min = 0;
            final int max = keys.isEmpty() ? 0 : keys.size() - 1;
            final TableSwitchInsnNode table = new TableSwitchInsnNode(min, max, default_case);

            // Load the actual argument onto the operand-stack and then branch.
            method.instructions.add(new VarInsnNode(Opcodes.ILOAD, 1));
            method.instructions.add(table);

            /**
             * Generate each case in the switch-case, except the default-case.
             */
            for (String element : keys)
            {
                /**
                 * Get the static-type of the element.
                 */
                final IVariableType element_type = typeOfElement(element);


                /**
                 * Mark the entry-point of the switch-case.
                 */
                final LabelNode label = new LabelNode();
                table.labels.add(label);
                method.instructions.add(label);

                /**
                 * Load the value of the element onto the operand-stack.
                 * This requires retrieving the value from the field it is stored in.
                 */
                method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // Load 'this'
                method.instructions.add(new FieldInsnNode(Opcodes.GETFIELD,
                                                          Utils.internalName(type),
                                                          nameOfField(element),
                                                          element_type.getDescriptor()));

                /**
                 * Box the value, if needed, so it can be added to the list.
                 */
                program.typesystem.utils.autoboxToObject(method.instructions, element_type);

                /**
                 * Return the result.
                 */
                method.instructions.add(new InsnNode(Opcodes.ARETURN));
            }

            /**
             * The default-case executes the same code that executes when the tuple is empty.
             */
            method.instructions.add(default_case);
        }

        /**
         * Throw an exception, if the element does not exist.
         */
        method.instructions.add(new VarInsnNode(Opcodes.ILOAD, 1));
        method.instructions.add(new LdcInsnNode(keys.size()));
        Utils.invoke(method.instructions,
                     Opcodes.INVOKESTATIC,
                     Helpers.class,
                     void.class,
                     "throwIndexOutOfBoundsException",
                     int.class,
                     int.class);

        /**
         * Add a return instruction, even though it will never actually execute.
         * This is needed, due to bytecode verification.
         */
        method.instructions.add(new InsnNode(Opcodes.ACONST_NULL));
        method.instructions.add(new InsnNode(Opcodes.ARETURN));

        return method;
    }

    /**
     * This method generates the bytecode representation of the keys() method.
     *
     * @return the generated method.
     */
    private MethodNode generateMethodKeys()
    {
        final MethodNode method = Utils.bytecodeOf(module,
                                                   TypeSystemUtils.find(type.getAllVisibleMethods(),
                                                                        "keys",
                                                                        "()Ljava/util/List;"));

        // Remove the abstract modifier.
        method.access = method.access & (~Opcodes.ACC_ABSTRACT);

        /**
         * The method simply retrieves the value in a static field and returns it.
         */
        method.instructions.add(new FieldInsnNode(Opcodes.GETSTATIC,
                                                  Utils.internalName(type),
                                                  "KEYS",
                                                  "Ljava/util/List;"));

        method.instructions.add(new InsnNode(Opcodes.ARETURN));

        return method;
    }

    /**
     * This method generates the bytecode representation of the types() method.
     *
     * @return the generated method.
     */
    private MethodNode generateMethodTypes()
    {
        final MethodNode method = Utils.bytecodeOf(module,
                                                   TypeSystemUtils.find(type.getAllVisibleMethods(),
                                                                        "types",
                                                                        "()Ljava/util/List;"));

        // Remove the abstract modifier.
        method.access = method.access & (~Opcodes.ACC_ABSTRACT);

        /**
         * The method simply retrieves the value in a static field and returns it.
         */
        method.instructions.add(new FieldInsnNode(Opcodes.GETSTATIC,
                                                  Utils.internalName(type),
                                                  "TYPES",
                                                  "Ljava/util/List;"));

        method.instructions.add(new InsnNode(Opcodes.ARETURN));

        return method;
    }

    /**
     * This method generates the bytecode representation of a setter method.
     *
     * <p>
     * A setter method must obtain a modifiable variant of the tuple.
     * In other words, the setter must copy the tuple, if it is immutable.
     * Then the setter must set the field in the tuple to the new value.
     * Finally, the setter must return the modified tuple.
     * Take note, the returned tuple may not be the original tuple.
     * </p>
     *
     * @param element is an object that describes the element.
     * @return the generated method.
     */
    private MethodNode generateSetter(final SetterMethod element)
    {
        /**
         * Get the static-type of the element.
         */
        final IVariableType element_type = typeOfElement(element.name);


        final MethodNode method = Utils.bytecodeOf(module,
                                                   TypeSystemUtils.find(type.getMethods(),
                                                                        element.name,
                                                                        "(" + element_type.getDescriptor() + ")" + type.getDescriptor()));

        // Remove the abstract modifier.
        method.access = method.access & (~Opcodes.ACC_ABSTRACT);

        /**
         * Get a variant of the tuple which can be modified.
         * If the original tuple is mutable, then this will simply be the original tuple.
         * If the original tuple is immutable, then this will be a copy of the original.
         */
        loadModifiableVariant(method.instructions);

        /**
         * Duplicate the object-reference that refers to the tuple.
         */
        method.instructions.add(new InsnNode(Opcodes.DUP));


        /**
         * Load the value, which will be assigned to the field, onto the operand-stack.
         */
        method.instructions.add(Utils.selectLoadVarInsn(element_type, 1));

        /**
         * Set the field that stores the value of the element.
         */
        method.instructions.add(new FieldInsnNode(Opcodes.PUTFIELD,
                                                  Utils.internalName(type),
                                                  nameOfField(element.name),
                                                  element_type.getDescriptor()));

        /**
         * Return the result.
         */
        method.instructions.add(new InsnNode(Opcodes.ARETURN));

        return method;
    }

    /**
     * This method generates bytecode that creates a copy of the record.
     *
     * <p>
     * This method expects that the record object is the topmost element of the operand-stack.
     * </p>
     *
     * @param code is the bytecode being generated.
     */
    private void loadModifiableVariant(final InsnList code)
    {
        // Generated Bytecode:
        //
        // NEW *type*              - Create a new uninitialized object of this record-type.
        // DUP                     - Duplicate the object-reference to the uninitialized object.
        //
        // ALOAD this              - Load 'this' onto the operand-stack.
        // GETFIELD this.field[0]  - Load value in field #0 onto the operand-stack.
        //
        // ALOAD this              - Load 'this' onto the operand-stack.
        // GETFIELD this.field[1]  - Load value in field #1 onto the operand-stack.
        //
        // ALOAD this              - Load 'this' onto the operand-stack.
        // GETFIELD this.field[2]  - Load value in field #2 onto the operand-stack.
        //
        // ...
        //
        // ALOAD this              - Load 'this' onto the operand-stack.
        // GETFIELD this.field[n]  - Load value in field #n onto the operand-stack.
        //
        // INVOKESPECIAL <init>    - Invoke the only ctor that the uninitialized object has.
        //                         - The constructor takes the previous field values as arguments.
        //                         - Essentially, we are simply copying the 'this' object.
        //                         - However, the ctor does *not* copy the special method bindings.
        //                         - So, we still need to do that.
        //
        // NOTE                    - The uninitialized object is now initialized.
        //                         - A reference to that object is on the top of the operand-stack.
        //
        ////////////////////////////////////////////////////////////////////////////////////////////


        /**
         * Create a new uninitialized instance of the tuple.
         */
        code.add(new TypeInsnNode(Opcodes.NEW, Utils.internalName(type)));

        /**
         * Duplicate the object-reference.
         */
        code.add(new InsnNode(Opcodes.DUP));

        /**
         * Load the value of each element onto the operand-stack.
         */
        for (String element : keys)
        {
            /**
             * Get the static-type of the element.
             */
            final IVariableType element_type = typeOfElement(element);

            /**
             * Load the value of the element onto the operand-stack.
             * This requires retrieving the value from the field it is stored in.
             */
            code.add(new VarInsnNode(Opcodes.ALOAD, 0)); // Load 'this'
            code.add(new FieldInsnNode(Opcodes.GETFIELD,
                                       Utils.internalName(type),
                                       nameOfField(element),
                                       element_type.getDescriptor()));
        }

        /**
         * Invoke the constructor in order to create a copy of this object.
         */
        code.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                    Utils.internalName(type),
                                    "<init>",
                                    ctor.getDescriptor()));
    }

    /**
     * This method generates the bytecode representation of a getter method.
     *
     * <p>
     * A getter method simply read the field that stores the element and then returns the result.
     * </p>
     *
     * @param element is an object that describes the element.
     * @return the generated method.
     */
    private MethodNode generateGetter(final GetterMethod element)
    {
        /**
         * Get the static-type of the element.
         */
        final IVariableType element_type = typeOfElement(element.name);


        final MethodNode method = Utils.bytecodeOf(module,
                                                   TypeSystemUtils.find(type.getMethods(),
                                                                        element.name,
                                                                        "()" + element_type.getDescriptor()));

        // Remove the abstract modifier.
        method.access = method.access & (~Opcodes.ACC_ABSTRACT);

        /**
         * The method simply retrieves the value in a field and returns it.
         */
        method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // Load 'this'
        method.instructions.add(new FieldInsnNode(Opcodes.GETFIELD,
                                                  Utils.internalName(type),
                                                  nameOfField(element.name),
                                                  element_type.getDescriptor()));

        /**
         * Return the result.
         */
        method.instructions.add(Utils.selectReturnInsn(element_type));

        return method;
    }

    /**
     * This method retrieves the static-type of an element given the element's name.
     *
     * @param key is the name of the element.
     * @return the most-specific static-type of the element.
     */
    private IVariableType typeOfElement(final String key)
    {
        final IVariableType result = analyzer.elements.get(key).type();

        return result;
    }
}