Utils.java

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

import autumn.lang.compiler.ast.commons.IConstruct;
import autumn.lang.internals.Helpers;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.mackenziehigh.autumn.lang.compiler.compilers.ModuleCompiler;
import com.mackenziehigh.autumn.lang.compiler.compilers.ProgramCompiler;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IArrayType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IClassType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IDeclaredType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IExpressionType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IInvokableMember;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IPrimitiveType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IReferenceType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IReturnType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IVariableType;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
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.LineNumberNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;

/**
 * This class contains utility methods needed during bytecode-generation.
 *
 * @author Mackenzie High
 */
public final class Utils
{
    public static final int IF_TRUE = Opcodes.IFNE;

    public static final int IF_FALSE = Opcodes.IFEQ;

    /**
     * These are the keywords in the Java programming language.
     */
    private static final Set<String> KEYWORDS = Sets.newTreeSet();

    static
    {
        KEYWORDS.add("abstract");
        KEYWORDS.add("assert");
        KEYWORDS.add("boolean");
        KEYWORDS.add("break");
        KEYWORDS.add("byte");
        KEYWORDS.add("case");
        KEYWORDS.add("catch");
        KEYWORDS.add("char");
        KEYWORDS.add("class");
        KEYWORDS.add("const");
        KEYWORDS.add("continue");
        KEYWORDS.add("default");
        KEYWORDS.add("do");
        KEYWORDS.add("double");
        KEYWORDS.add("else");
        KEYWORDS.add("enum");
        KEYWORDS.add("extends");
        KEYWORDS.add("final");
        KEYWORDS.add("finally");
        KEYWORDS.add("float");
        KEYWORDS.add("for");
        KEYWORDS.add("goto");
        KEYWORDS.add("if");
        KEYWORDS.add("implements");
        KEYWORDS.add("import");
        KEYWORDS.add("instanceof");
        KEYWORDS.add("int");
        KEYWORDS.add("interface");
        KEYWORDS.add("long");
        KEYWORDS.add("native");
        KEYWORDS.add("new");
        KEYWORDS.add("package");
        KEYWORDS.add("private");
        KEYWORDS.add("protected");
        KEYWORDS.add("public");
        KEYWORDS.add("return");
        KEYWORDS.add("short");
        KEYWORDS.add("static");
        KEYWORDS.add("strictfp");
        KEYWORDS.add("super");
        KEYWORDS.add("switch");
        KEYWORDS.add("synchronized");
        KEYWORDS.add("this");
        KEYWORDS.add("throw");
        KEYWORDS.add("throws");
        KEYWORDS.add("transient");
        KEYWORDS.add("try");
        KEYWORDS.add("void");
        KEYWORDS.add("volatile");
        KEYWORDS.add("while");
        KEYWORDS.add("false");
        KEYWORDS.add("true");
        KEYWORDS.add("null");
    }

    /**
     * This method computes the internal name of a type.
     *
     * <p>
     * Example: "java.lang.String" returns "java/lang/String" <br>
     * Example: "java.lang.String[]" returns "[Ljava/lang/String;" <br>
     * </p>
     *
     * @param type is the type whose internal name is requested.
     * @return the internal name of the type.
     */
    public static String internalName(final IReferenceType type)
    {
        Preconditions.checkNotNull(type);

        assert type.isNullType() == false;

        if (type.isArrayType())
        {
            return type.getDescriptor();
        }
        else
        {
            final String descriptor = type.getDescriptor();

            final String result = descriptor.substring(1).replace(";", "");

            return result;
        }
    }

    /**
     * This method computes the simple name of a type.
     *
     * <p>
     * Example: "java.lang.String" returns "String" <br>
     * Example: "java.lang.String[]" returns "String[]" <br>
     * </p>
     *
     * @param type is the type whose simple name is requested.
     * @return the simple name of the type.
     */
    public static String simpleName(final IExpressionType type)
    {
        Preconditions.checkNotNull(type);

        if (type.isNullType())
        {
            return "null";
        }

        if (type.isVoidType())
        {
            return "void";
        }

        final String source = sourceName(type);

        final String[] parts = source.split("\\.");

        final String name = parts[parts.length - 1];

        return name;
    }

    /**
     * This method computes the name of a type as it appears in the source-code.
     *
     * @param type is the type whose name is requested.
     * @return the name of the type as it appears in source-code.
     */
    public static String sourceName(final IExpressionType type)
    {
        Preconditions.checkNotNull(type);

        if (type.isNullType())
        {
            return "null";
        }
        else if (type.isVoidType())
        {
            return "void";
        }
        else if (type.isPrimitiveType())
        {
            return ((IPrimitiveType) type).toClass().getName();
        }
        else if (type.isReferenceType() && ((IReferenceType) type).isArrayType())
        {
            final IArrayType array = (IArrayType) type;

            final String element = sourceName(array.getElement());

            final String dimensions = Strings.repeat("[]", array.getDimensions());

            return element + dimensions;
        }
        else
        {
            final String result = ((IDeclaredType) type).getDescriptor()
                    .substring(1)
                    .replace(";", "")
                    .replace("/", ".");

            return result;
        }
    }

    /**
     * This method selects the appropriate "return" bytecode instruction for a given type.
     *
     * <p>
     * This method will return a RETURN instruction, if the type is void.
     * </p>
     *
     * @param type is the type of value that will be returned.
     * @return the bytecode "return" instruction that is used for the given type.
     */
    public static AbstractInsnNode selectReturnInsn(final IReturnType type)
    {
        if (type.isVoidType())
        {
            return new InsnNode(Opcodes.RETURN);
        }
        else if (type.isReferenceType())
        {
            return new InsnNode(Opcodes.ARETURN);
        }
        else if (type.getDescriptor().equals("J"))
        {
            return new InsnNode(Opcodes.LRETURN);
        }
        else if (type.getDescriptor().equals("F"))
        {
            return new InsnNode(Opcodes.FRETURN);
        }
        else if (type.getDescriptor().equals("D"))
        {
            return new InsnNode(Opcodes.DRETURN);
        }
        else // booleans, chars, bytes, shorts, and ints
        {
            return new InsnNode(Opcodes.IRETURN);
        }
    }

    /**
     * This method selects the appropriate bytecode instruction to load a variable.
     *
     * @param type is the type of the variable.
     * @param address is the address of the variable.
     * @return the appropriate load instruction.
     */
    public static AbstractInsnNode selectLoadVarInsn(final IVariableType type,
                                                     final int address)
    {
        if (type.getDescriptor().equals("Z"))
        {
            return new VarInsnNode(Opcodes.ILOAD, address);
        }
        else if (type.getDescriptor().equals("C"))
        {
            return new VarInsnNode(Opcodes.ILOAD, address);
        }
        else if (type.getDescriptor().equals("B"))
        {
            return new VarInsnNode(Opcodes.ILOAD, address);
        }
        else if (type.getDescriptor().equals("S"))
        {
            return new VarInsnNode(Opcodes.ILOAD, address);
        }
        else if (type.getDescriptor().equals("I"))
        {
            return new VarInsnNode(Opcodes.ILOAD, address);
        }
        else if (type.getDescriptor().equals("J"))
        {
            return new VarInsnNode(Opcodes.LLOAD, address);
        }
        else if (type.getDescriptor().equals("F"))
        {
            return new VarInsnNode(Opcodes.FLOAD, address);
        }
        else if (type.getDescriptor().equals("D"))
        {
            return new VarInsnNode(Opcodes.DLOAD, address);
        }
        else
        {
            return new VarInsnNode(Opcodes.ALOAD, address);
        }
    }

    /**
     * This method selects the appropriate bytecode instruction to store a value in a variable.
     *
     * @param type is the type of the variable.
     * @param address is the address of the variable.
     * @return the appropriate store instruction.
     */
    public static AbstractInsnNode selectStoreVarInsn(final IVariableType type,
                                                      final int address)
    {
        if (type.getDescriptor().equals("Z"))
        {
            return new VarInsnNode(Opcodes.ISTORE, address);
        }
        else if (type.getDescriptor().equals("C"))
        {
            return new VarInsnNode(Opcodes.ISTORE, address);
        }
        else if (type.getDescriptor().equals("B"))
        {
            return new VarInsnNode(Opcodes.ISTORE, address);
        }
        else if (type.getDescriptor().equals("S"))
        {
            return new VarInsnNode(Opcodes.ISTORE, address);
        }
        else if (type.getDescriptor().equals("I"))
        {
            return new VarInsnNode(Opcodes.ISTORE, address);
        }
        else if (type.getDescriptor().equals("J"))
        {
            return new VarInsnNode(Opcodes.LSTORE, address);
        }
        else if (type.getDescriptor().equals("F"))
        {
            return new VarInsnNode(Opcodes.FSTORE, address);
        }
        else if (type.getDescriptor().equals("D"))
        {
            return new VarInsnNode(Opcodes.DSTORE, address);
        }
        else // (type.isReferenceType())
        {
            return new VarInsnNode(Opcodes.ASTORE, address);
        }

    }

    /**
     * This method creates a bytecode instruction that loads a class-literal onto the operand-stack.
     *
     * @param type is the type that the class-literal represents.
     * @return the aforedescribed bytecode instruction.
     */
    public static AbstractInsnNode ldcClass(final IType type)
    {
        final String descriptor = type.getDescriptor();

        final Type klass = Type.getType(descriptor);

        final Map<String, String> predefined = Maps.newTreeMap();
        predefined.put("Z", Type.getInternalName(Boolean.class));
        predefined.put("C", Type.getInternalName(Character.class));
        predefined.put("B", Type.getInternalName(Byte.class));
        predefined.put("S", Type.getInternalName(Short.class));
        predefined.put("I", Type.getInternalName(Integer.class));
        predefined.put("J", Type.getInternalName(Long.class));
        predefined.put("F", Type.getInternalName(Float.class));
        predefined.put("D", Type.getInternalName(Double.class));
        predefined.put("V", Type.getInternalName(Void.class));


        if (predefined.containsKey(klass.getDescriptor()))
        {
            return new FieldInsnNode(Opcodes.GETSTATIC,
                                     predefined.get(klass.getDescriptor()),
                                     "TYPE",
                                     Type.getDescriptor(Class.class));
        }
        else
        {
            return new LdcInsnNode(klass);
        }
    }

    /**
     * This method determines the amount of storage needed for a variable.
     *
     * @param type is the type whose size will be determined.
     * @return the amount of space needed to store a value of the given type.
     */
    public static int sizeof(final IType type)
    {
        if (type.getDescriptor().equals("Z"))
        {
            return 1;
        }
        else if (type.getDescriptor().equals("C"))
        {
            return 1;
        }
        else if (type.getDescriptor().equals("B"))
        {
            return 1;
        }
        else if (type.getDescriptor().equals("S"))
        {
            return 1;
        }
        else if (type.getDescriptor().equals("I"))
        {
            return 1;
        }
        else if (type.getDescriptor().equals("J"))
        {
            return 2;
        }
        else if (type.getDescriptor().equals("F"))
        {
            return 1;
        }
        else if (type.getDescriptor().equals("D"))
        {
            return 2;
        }
        else if (type.getDescriptor().equals("V"))
        {
            return 0;
        }
        else // (type.isReferenceType())
        {
            return 1;
        }

    }

    /**
     * This method selects a bytecode instruction to pop a value off of the operand-stack.
     *
     * <p>
     * This method returns a NOP instruction, if the type is the void type.
     * </p>
     *
     * @param type is the type of value that will be popped off of the stack.
     * @return the aforedescribed bytecode instruction.
     */
    public static AbstractInsnNode selectPop(final IType type)
    {
        if ("V".equals(type.getDescriptor())) // void
        {
            return new InsnNode(Opcodes.NOP); // There is no value to pop, so return nop.
        }
        else if ("J".equals(type.getDescriptor())) // long
        {
            return new InsnNode(Opcodes.POP2);
        }
        else if ("D".equals(type.getDescriptor())) // double
        {
            return new InsnNode(Opcodes.POP2);
        }
        else // boolean, char, byte, short, int, and object references
        {
            return new InsnNode(Opcodes.POP);
        }
    }

    /**
     * This method retrieves the set of words that are reserved in the Java programming language.
     *
     * @return the immutable set of reserved words.
     */
    public static Set<String> keywords()
    {
        return Collections.unmodifiableSet(KEYWORDS);
    }

    /**
     * This method determines whether a word is a keyword in the Java programming language.
     *
     * @param word is the word that may be a keyword.
     * @return true, iff the word is a reserved keyword.
     */
    public static boolean isKeyword(final String word)
    {
        return keywords().contains(word);
    }

    /**
     * This method selects a bytecode instruction to push a default value onto the operand-stack.
     *
     * @param type is the type of the default value.
     * @return the aforedescribed instruction.
     */
    public static AbstractInsnNode ldcDefault(final IType type)
    {
        if ("Z".equals(type.getDescriptor()))
        {
            return new LdcInsnNode(false);
        }
        else if ("C".equals(type.getDescriptor()))
        {
            return new LdcInsnNode((char) 0);
        }
        else if ("B".equals(type.getDescriptor()))
        {
            return new LdcInsnNode((byte) 0);
        }
        else if ("S".equals(type.getDescriptor()))
        {
            return new LdcInsnNode((short) 0);
        }
        else if ("I".equals(type.getDescriptor()))
        {
            return new LdcInsnNode((int) 0);
        }
        else if ("J".equals(type.getDescriptor()))
        {
            return new LdcInsnNode((long) 0);
        }
        else if ("F".equals(type.getDescriptor()))
        {
            return new LdcInsnNode((float) 0);
        }
        else if ("D".equals(type.getDescriptor()))
        {
            return new LdcInsnNode((double) 0);
        }
        else // object references
        {
            return new InsnNode(Opcodes.ACONST_NULL);
        }
    }

    /**
     * This method generates bytecode to invoke the "push" method of an ArgumentStack.
     *
     * @param program is the program being compiled.
     * @param code is the list of generated bytecode to add instructions to.
     * @param type is the type of the value being pushed.
     */
    public static void pushArgument(final ProgramCompiler program,
                                    final InsnList code,
                                    final IExpressionType type)
    {
        Preconditions.checkNotNull(type);

        // Assume: The ArgumentStack is already on the operand-stack.
        // Assume: The argument is already on the operand-stack.

        final String OBJECT = "Ljava/lang/Object;";

        final String owner = "autumn/lang/internals/ArgumentStack";
        final String name = "push";
        final String desc = "(" + (type.isPrimitiveType() ? type.getDescriptor() : OBJECT) + ")V";

        // Invoke the push method in order to push the argument onto the argument-stack.
        code.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, owner, name, desc));
    }

    /**
     * This method generates bytecode to retrieve a value from an ArgumentStack.
     *
     * <p>
     * If the value is a reference type, then it will be downcast.
     * </p>
     *
     * @param program is the program being compiled.
     * @param code is the list of generated bytecode to add instructions to.
     * @param type is the type of the value that will be retrieved.
     */
    public static void peekArgument(final ProgramCompiler program,
                                    final InsnList code,
                                    final IExpressionType type)
    {
        // Assume: The ArgumentStack is already on the operand-stack.

        final String OBJECT = "Ljava/lang/Object;";

        // Invoke the peek method in order to retrive the value.
        final String owner = Utils.internalName(program.typesystem.utils.ARGUMENT_STACK);
        final String name = "peek" + (type.isPrimitiveType() ? type.getDescriptor() : "O");
        final String desc = "()" + (type.isPrimitiveType() ? type.getDescriptor() : OBJECT);
        code.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, owner, name, desc));

        // If the type is a reference-type, then downcast the value.
        if (type.isReferenceType())
        {
            final String cast = Utils.internalName((IReferenceType) type);
            code.add(new TypeInsnNode(Opcodes.CHECKCAST, cast));
        }
    }

    /**
     * This method generates bytecode to pop a value off of an ArgumentStack.
     *
     * @param program is the program being compiled.
     * @param code is the list of generated bytecode to add instructions to.
     */
    public static void popArgument(final ProgramCompiler program,
                                   final InsnList code)
    {
        // Invoke the peek method in order to retrive the value.
        final String owner = Utils.internalName(program.typesystem.utils.ARGUMENT_STACK);
        final String name = "pop";
        final String desc = "()V";
        code.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, owner, name, desc));
    }

    /**
     * This method generates bytecode to retrieve a value from an ArgumentStack at a given index.
     *
     * <p>
     * The index is numbered from the base of the stack.
     * So, index zero is the bottommost element.
     * </p>
     *
     * <p>
     * If the value is a reference type, then it will be downcast.
     * </p>
     *
     * @param program is the program being compiled.
     * @param code is the list of generated bytecode to add instructions to.
     * @param type is the type of the value that will be retrieved.
     * @param index is the index of the argument to retrieve.
     */
    public static void getArgument(final ProgramCompiler program,
                                   final InsnList code,
                                   final IExpressionType type,
                                   final int index)
    {
        // Assume: The ArgumentStack is already on the operand-stack.

        final String OBJECT = "Ljava/lang/Object;";

        // Load the index onto the operand-stack.
        code.add(new LdcInsnNode(index));

        // Invoke the get method in order to retrive the value.
        final String owner = Utils.internalName(program.typesystem.utils.ARGUMENT_STACK);
        final String name = "get" + (type.isPrimitiveType() ? type.getDescriptor() : "O");
        final String desc = "(I)" + (type.isPrimitiveType() ? type.getDescriptor() : OBJECT);
        code.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, owner, name, desc));

        // If the type is a reference-type, then downcast the value.
        if (type.isReferenceType())
        {
            final String cast = Utils.internalName((IReferenceType) type);
            code.add(new TypeInsnNode(Opcodes.CHECKCAST, cast));
        }
    }

    /**
     * This method appends a list of instructions onto another list of instructions.
     *
     * @param out is the list that will have elements added to it.
     * @param addendum are the elements to add to the other list.
     */
    public static void appendToInsnList(final InsnList out,
                                        final Iterable<AbstractInsnNode> addendum)
    {
        for (AbstractInsnNode node : addendum)
        {
            out.add(node);
        }
    }

    /**
     * This method generates the bytecode needed to load a delegate onto the operand-stack.
     *
     * @param code is the list of bytecode instructions to append the generated code onto.
     * @param module is the type of the module that contains the delegated function.
     * @param method is the name of the delegated function.
     */
    public static void loadDelegate(final InsnList code,
                                    final IClassType module,
                                    final String method)
    {
        String owner;
        String name;
        String desc;

        /**
         * Load the an instance of the module onto the operand-stack.
         */
        owner = Utils.internalName(module);
        name = "instance";
        desc = "()Lautumn/lang/Module;";
        code.add(new MethodInsnNode(Opcodes.INVOKESTATIC, owner, name, desc));

        /**
         * Load the name of the delegated method onto the operand-stack.
         */
        code.add(new LdcInsnNode(method));

        /**
         * Invoke the helper utility method that resolves the delegate.
         */
        owner = "Lautumn/lang/internals/Helpers;";
        name = "delegate";
        desc = "(Lautumn/lang/Module;Ljava/lang/String;)Lautumn/lang/Delegate;";
        code.add(new MethodInsnNode(Opcodes.INVOKESTATIC, owner, name, desc));
    }

    /**
     * This method creates the bytecode representation of a constructor or method.
     *
     * @param module is essentially the module that is being compiled.
     * @param member is the type-system representation of the constructor or method.
     * @return the bytecode representation of the
     */
    public static MethodNode bytecodeOf(final ModuleCompiler module,
                                        final IInvokableMember member)
    {
        Preconditions.checkNotNull(module);
        Preconditions.checkNotNull(member);

        final MethodNode node = new MethodNode();

        node.visibleAnnotations = module.anno_utils.compileAnnotationList(member.getAnnotations());
        node.access = member.getModifiers();
        node.name = member.getName();
        node.desc = member.getDescriptor();
        node.exceptions = Lists.newLinkedList();

        for (IClassType exception : member.getThrowsClause())
        {
            node.exceptions.add(Utils.internalName(exception));
        }

        return node;
    }

    /**
     * This method creates a bytecode instruction that invokes a method.
     *
     * @param code is the list of bytecode instruction to append the instruction onto.
     * @param opcode is the opcode of the new instruction.
     * @param owner is the type where the method is declared.
     * @param returns is the return type of the method.
     * @param name is the name of the method.
     * @param parameters are the types of the method's parameters.
     */
    public static void invoke(final InsnList code,
                              final int opcode,
                              final Class owner,
                              final Class returns,
                              final String name,
                              final Class... parameters)
    {
        final StringBuilder descriptor = new StringBuilder();

        descriptor.append("(");
        {
            for (Class param : parameters)
            {
                descriptor.append(Type.getDescriptor(param));
            }
        }
        descriptor.append(")");

        descriptor.append(Type.getDescriptor(returns));

        final Type type = Type.getType(owner);

        code.add(new MethodInsnNode(opcode, type.getInternalName(), name, descriptor.toString()));
    }

    /**
     * This method generates bytecode that makes a list immutable.
     *
     * @param code is the list of bytecode instructions to append the instructions onto.
     */
    public static void makeListImmutable(final InsnList code)
    {
        Utils.invoke(code,
                     Opcodes.INVOKESTATIC,
                     Helpers.class,
                     List.class,
                     "newImmutableList",
                     Iterable.class);
    }

    /**
     * This method generates bytecode that creates an immutable map from two lists.
     *
     * <p>
     * The map will preserve the order of the keys.
     * </p>
     *
     * @param code is the list of bytecode instructions to append the instructions onto.
     */
    public static void createImmutableMap(final InsnList code)
    {
        Utils.invoke(code,
                     Opcodes.INVOKESTATIC,
                     Helpers.class,
                     Map.class,
                     "newImmutableMap",
                     List.class,
                     List.class);
    }

    /**
     * This method generates the bytecode necessary to load the current thread's
     * argument-stack onto the operand-stack.
     *
     * @param code is the code being generated.
     */
    public static void loadArgumentStack(final InsnList code)
    {
        code.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                                    "autumn/lang/internals/ArgumentStack",
                                    "getThreadStack",
                                    "()Lautumn/lang/internals/ArgumentStack;"));
    }

    /**
     * This method adds a LineNumberNode to a list of bytecode instructions.
     *
     * @param code is the bytecode being generated.
     * @param construct is the construct that provides the line number.
     */
    public static void addLineNumber(final InsnList code,
                                     final IConstruct construct)
    {
        if (construct.getLocation() == null)
        {
            return;
        }

        final LabelNode label = new LabelNode();

        code.add(label);
        code.add(new LineNumberNode(construct.getLocation().getLine(), label));
    }

    /**
     * This method conditionally generates a checked-cast instruction.
     *
     * <p>
     * In other words, this method will not generate a clearly unnecessary or impossible cast.
     * </p>
     *
     * @param value is the type of the value being cast.
     * @param type is the type to cast the value to.
     * @return the generated instruction.
     */
    public static AbstractInsnNode conditionalCast(final IExpressionType value,
                                                   final IExpressionType type)
    {
        if (value.isReferenceType() == false || type.isReferenceType() == false)
        {
            return new InsnNode(Opcodes.NOP);
        }
        else if (value.isSubtypeOf(type))
        {
            return new InsnNode(Opcodes.NOP);
        }
        else
        {
            return new TypeInsnNode(Opcodes.CHECKCAST, Utils.internalName((IReferenceType) type));
        }
    }
}