public class Ruby {
    private Context context;
    private RubyClass objectClass, moduleClass, classClass,
        numericClass, integerClass, fixnumClass, bignumClass,
        floatClass, stringClass, symbolClass,
        booleanClass, trueClass, falseClass, nilClass,
        arrayClass, hashClass, rangeClass,
        regexpClass, matchDataClass,
        exceptionClass, fatalClass,
        procClass;
    private IRubyObject nilObject, trueObject, falseObject;
    private IRubyObject topSelf;
    private Map<String, IRubyObject> globalVars;

    public Ruby() {
        objectClass = RubyClass.newBootClass(this, "Object", null);
        moduleClass = RubyClass.newBootClass(this, "Module", objectClass);
        classClass = RubyClass.newBootClass(this, "Class", moduleClass);

        objectClass.setMetaClass(classClass);
        moduleClass.setMetaClass(classClass);
        classClass.setMetaClass(classClass);

        RubyClass metaClass;
        metaClass = objectClass.makeMetaClass(classClass);
        metaClass = moduleClass.makeMetaClass(metaClass);
        metaClass = classClass.makeMetaClass(metaClass);

        numericClass = RubyClass.newClass(this, "Numeric", objectClass);
        integerClass = RubyClass.newClass(this, "Integer", numericClass);
        fixnumClass = RubyClass.newClass(this, "Fixnum", integerClass);
        bignumClass = RubyClass.newClass(this, "Bignum", integerClass);
        floatClass = RubyClass.newClass(this, "Float", numericClass);
        stringClass = RubyClass.newClass(this, "String", objectClass);
        symbolClass = RubyClass.newClass(this, "Symbol", objectClass);
        nilClass = RubyClass.newClass(this, "NilClass", objectClass);
        booleanClass = RubyClass.newClass(this, "Boolean", objectClass);
        trueClass = RubyClass.newClass(this, "TrueClass", booleanClass);
        falseClass = RubyClass.newClass(this, "FalseClass", booleanClass);
        arrayClass = RubyClass.newClass(this, "Array", objectClass);
        hashClass = RubyClass.newClass(this, "Hash", objectClass);
        rangeClass = RubyClass.newClass(this, "Range", objectClass);
        regexpClass = RubyClass.newClass(this, "Regexp", objectClass);
        matchDataClass = RubyClass.newClass(this, "MatchData", objectClass);
        exceptionClass = RubyClass.newClass(this, "Exception", objectClass);
        procClass = RubyClass.newClass(this, "Proc", objectClass);
        fatalClass = RubyClass.newClass(this, "fatal", exceptionClass);

        objectClass.setConstant("Object", objectClass);
        objectClass.setConstant("Module", moduleClass);
        objectClass.setConstant("Class", classClass);
        objectClass.setConstant("Numeric", numericClass);
        objectClass.setConstant("Integer", integerClass);
        objectClass.setConstant("Fixnum", fixnumClass);
        objectClass.setConstant("Bignum", bignumClass);
        objectClass.setConstant("Float", floatClass);
        objectClass.setConstant("String", stringClass);
        objectClass.setConstant("Symbol", symbolClass);
        objectClass.setConstant("NilClass", nilClass);
        objectClass.setConstant("Boolean", booleanClass);
        objectClass.setConstant("TrueClass", trueClass);
        objectClass.setConstant("FalseClass", falseClass);
        objectClass.setConstant("Array", arrayClass);
        objectClass.setConstant("Hash", hashClass);
        objectClass.setConstant("Range", rangeClass);
        objectClass.setConstant("Regexp", regexpClass);
        objectClass.setConstant("MatchData", matchDataClass);
        objectClass.setConstant("Exception", exceptionClass);
        objectClass.setConstant("Proc", procClass);
        objectClass.setConstant("fatal", fatalClass);

        nilObject = new SpecialObject(this, nilClass, "nil");
        trueObject = new SpecialObject(this, trueClass, "true");
        falseObject = new SpecialObject(this, falseClass, "false");

        topSelf = new RubyObject(this, objectClass);

        globalVars = new HashMap<String, IRubyObject>();
        context = new Context(this);
    }

    public IRubyObject getGlobalVar(String name) {
        return globalVars.get(name);
    }

    public void setGlobalVar(String name, IRubyObject value) {
        globalVars.put(name, value);
    }

    public Context getContext() {
        return context;
    }

    public boolean isInstanceOf(IRubyObject object, RubyModule klass) {
        return object.getMetaClass() == klass;
    }

    public boolean isKindOf(IRubyObject object, RubyModule klass) {
        // FIXME speedup
        RubyClass oclass = object.getMetaClass();
        while (oclass != null) {
            if (oclass == klass) {
                return true;
            }
            oclass = oclass.getSuperClass();
        }
        if (object.getMetaClass().getIncludes(true).contains(klass)) {
            return true;
        }
        return false;
    }

    public RubyClass getObject() {
        return objectClass;
    }

    public RubyClass getModule() {
        return moduleClass;
    }

    public RubyClass getClassClass() {
        return classClass;
    }

    public RubyClass getNumeric() {
        return numericClass;
    }

    public RubyClass getInteger() {
        return integerClass;
    }

    public RubyClass getFixnum() {
        return fixnumClass;
    }

    public RubyClass getBignum() {
        return bignumClass;
    }

    public RubyClass getFloat() {
        return floatClass;
    }

    public RubyClass getString() {
        return stringClass;
    }

    public RubyClass getSymbol() {
        return symbolClass;
    }

    public RubyClass getArray() {
        return arrayClass;
    }

    public RubyClass getHash() {
        return hashClass;
    }

    public RubyClass getRange() {
        return rangeClass;
    }

    public RubyClass getRegexp() {
        return regexpClass;
    }

    public RubyClass getMatchData() {
        return matchDataClass;
    }

    public RubyClass getNilClass() {
        return nilClass;
    }

    public RubyClass getBoolean() {
        return booleanClass;
    }

    public RubyClass getTrueClass() {
        return trueClass;
    }

    public RubyClass getFalseClass() {
        return falseClass;
    }

    public RubyClass getProc() {
        return procClass;
    }

    public IRubyObject getNil() {
        return nilObject;
    }

    public IRubyObject getTrue() {
        return trueObject;
    }

    public IRubyObject getFalse() {
        return falseObject;
    }

    public IRubyObject getTopSelf() {
        return topSelf;
    }
} public class RubyClass extends RubyModule {
    protected RubyClass superClass;

    public static RubyClass newClass(Ruby runtime, String baseName, RubyClass superClass) {
        return newClass(runtime, baseName, superClass, null);
    }

    public static RubyClass newClass(Ruby runtime, String baseName, RubyClass superClass, RubyModule parent) {
        if (superClass == null) {
            superClass = runtime.getObject();
        }
        RubyClass klass = new RubyClass(runtime, superClass, parent);
        klass.setBaseName(baseName);
        klass.makeMetaClass(superClass.getMetaClass());
        return klass;
    }

    public static RubyClass newBootClass(Ruby runtime, String baseName, RubyClass superClass) {
        RubyClass klass = new RubyClass(runtime, superClass);
        klass.setBaseName(baseName);
        return klass;
    }

    protected RubyClass(Ruby runtime, RubyClass superClass) {
        this(runtime, superClass, null);
    }
    
    protected RubyClass(Ruby runtime, RubyClass superClass, RubyModule parent) {
        this(runtime, runtime.getClassClass(), superClass, parent);
    }

    protected RubyClass(Ruby runtime, RubyClass metaClass, RubyClass superClass, RubyModule parent) {
        super(runtime, metaClass, parent);
        this.superClass = superClass;
    }

    public RubyClass getRealClass() {
        return this;
    }
    
    public RubyClass getSuperClass() {
        return superClass;
    }

    public void setSuperClass(RubyClass superClass) {
        this.superClass = superClass;
    }

    public boolean isSingleton() {
        return false;
    }

    public void addSubclass(RubyClass subclass) {
        // FIXME
    }

    @Override
    public IRubyObject getConstant(String name) {
        IRubyObject constant = super.getConstant(name);
        if (constant == null && superClass != null && this != getRuntime().getObject()) {
            constant = superClass.getConstant(name);
        }
        return constant;
    }

    @Override
    public Set<String> getConstants(boolean inheritedToo) {
        Set<String> result = super.getConstants(inheritedToo);
        RubyClass sclass = superClass;
        while (sclass != null) {
            result.addAll(sclass.getConstants(inheritedToo));
            sclass = sclass.getSuperClass();
        }
        return result;
    }

    @Override
    public RubyModule getConstantModule(String name) {
        RubyModule module = super.getConstantModule(name);
        RubyClass sclass = superClass;
        while (module == null && sclass != null) {
            module = sclass.getConstantModule(name);
            sclass = sclass.getSuperClass();
        }
        return module;
    }

    @Override
    public DynamicMethod searchMethod(String name) {
        DynamicMethod method = getMethod(name);
        if (method == null && superClass != null) {
            method = superClass.searchMethod(name);
        }
        if (method == null) {
            method = super.searchMethod(name);
        }
        return method;
    }

    @Override
    public Set<String> getMethods(boolean inheritedToo) {
        Set<String> result = super.getMethods(inheritedToo);
        RubyClass sclass = superClass;
        while (sclass != null) {
            result.addAll(sclass.getPublicMethods(inheritedToo));
            sclass = sclass.getSuperClass();
        }
        return result;
    }

    @Override
    public List<RubyModule> getIncludes(boolean inheritedToo) {
        List<RubyModule> result = super.getIncludes(inheritedToo);
        if (inheritedToo) {
            result = new ArrayList<RubyModule>(result);
            RubyClass sclass = superClass;
            while (sclass != null) {
                result.addAll(sclass.getIncludes(inheritedToo));
                sclass = sclass.getSuperClass();
            }
        }
        return result;
    }
} public class RubyModule extends RubyObject {
    private String baseName;
    private RubyModule parent;
    private Map<String, DynamicMethod> methods;
    private Map<String, IRubyObject> constants;
    private Map<String, IRubyObject> classVars;
    private List<RubyModule> includes;

    public static RubyModule newModule(Ruby runtime, String baseName, RubyModule parent) {
        RubyModule module = new RubyModule(runtime, parent);
        module.setBaseName(baseName);
        module.makeMetaClass(runtime.getModule());
        return module;
    }

    protected RubyModule(Ruby runtime) {
        this(runtime, null);
    }
    
    protected RubyModule(Ruby runtime, RubyModule parent) {
        this(runtime, runtime.getModule(), parent);
    }
    
    protected RubyModule(Ruby runtime, RubyClass metaClass, RubyModule parent) {
        super(runtime, metaClass);
        this.parent = parent;
        this.methods = new HashMap<String, DynamicMethod>();
        this.constants = new HashMap<String, IRubyObject>();
        this.classVars = new HashMap<String, IRubyObject>();
        this.includes = new ArrayList<RubyModule>();
    }

    public String getBaseName() {
        return baseName;
    }

    public void setBaseName(String baseName) {
        this.baseName = baseName;
    }

    public RubyModule getParent() {
        return parent;
    }

    public void setConstant(String name, IRubyObject constant) {
        constants.put(name, constant);
    }

    public IRubyObject getConstant(String name) {
        return getConstant(name, null);
    }

    private IRubyObject getConstant(String name, Set<RubyModule> seen) {
        if (seen != null && seen.contains(this)) {
            return null;
        }

        IRubyObject constant = getConstantUnder(name);
        if (constant == null && parent != null) {
            constant = parent.getConstant(name, seen);
        }
        if (constant == null) {
            if (seen == null) {
                seen = new HashSet<RubyModule>();
            }
            seen.add(this);
            for (RubyModule module : includes) {
                constant = module.getConstant(name, seen);
                if (constant != null) {
                    return constant;
                }
            }
        }
        return constant;
    }

    public IRubyObject getConstantUnder(String name) {
        return constants.get(name);
    }

    public Set<String> getConstants(boolean inheritedToo) {
        Set<String> result = new HashSet<String>(constants.keySet());
        if (inheritedToo) {
            for (RubyModule module : includes) {
                result.addAll(module.getConstants(inheritedToo));
            }
        }
        return result;
    }

    public RubyModule getConstantModule(String name) {
        // FIXME return pairs at getConstants
        if (constants.containsKey(name)) {
            return this;
        } else {
            for (RubyModule module : includes) {
                module = module.getConstantModule(name);
                if (module != null) {
                    return module;
                }
            }
        }
        return null;
    }

    public boolean isConstantDefined(String name) {
        return constants.containsKey(name);
    }

    public IRubyObject getClassVar(String name) {
        return classVars.get(name);
    }

    public void setClassVar(String name, IRubyObject value) {
        classVars.put(name, value);
    }

    public RubyClass defineOrGetClassUnder(String name, RubyClass superClass) {
        if (isConstantDefined(name)) {
            IRubyObject object = getConstant(name);
            if (object instanceof RubyClass) {
                return (RubyClass) object;
            } else {
                Logger.error("%s is not class", name);
                return null;
            }
        } else {
            RubyClass klass = RubyClass.newClass(getRuntime(), name, superClass, this);
            setConstant(name, klass);
            return klass;
        }
    }

    public RubyModule defineOrGetModuleUnder(String name) {
        if (isConstantDefined(name)) {
            IRubyObject object = getConstant(name);
            if (object.getClass() == RubyModule.class) {
                return (RubyModule) object;
            } else {
                Logger.error("%s is not module", name);
                return null;
            }
        } else {
            RubyModule module = RubyModule.newModule(getRuntime(), name, this);
            setConstant(name, module);
            return module;
        }
    }

    public void addMethod(String name, DynamicMethod method) {
        methods.put(name, method);
    }

    public DynamicMethod getMethod(String name) {
        return methods.get(name);
    }

    public Set<String> getMethods(boolean inheritedToo) {
        Set<String> result = new HashSet<String>(methods.keySet());
        if (inheritedToo) {
            for (RubyModule module : includes) {
                result.addAll(module.getPublicMethods(inheritedToo));
            }
        }
        return result;
    }

    public Set<String> getPublicMethods(boolean inheritedToo) {
        Set<String> result = getVisibleMethods(Visibility.PUBLIC);
        if (inheritedToo) {
            for (RubyModule module : includes) {
                result.addAll(module.getPublicMethods(inheritedToo));
            }
        }
        return result;
    }

    private Set<String> getVisibleMethods(Visibility visibility) {
        Set<String> result = new HashSet<String>();
        for (Map.Entry<String, DynamicMethod> entry : methods.entrySet()) {
            if (entry.getValue().getVisibility() == visibility) {
                result.add(entry.getKey());
            }
        }
        return result;
    }

    public DynamicMethod searchMethod(String name) {
        DynamicMethod method = getMethod(name);
        if (method == null) {
            for (RubyModule module : includes) {
                if ((method = module.searchMethod(name)) != null) {
                    break;
                }
            }
        }
        return method;
    }

    public void includeModule(RubyModule module) {
        includes.add(module);
    }

    public List<RubyModule> getIncludes(boolean inheritedToo) {
        return includes;
    }

    public String toMethodPathString() {
        return toString() + "#";
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        if (parent != null && parent != getRuntime().getObject()) {
            sb.append(parent.toString());
            sb.append("::");
        }
        sb.append(baseName != null ? baseName : super.toString());
        return sb.toString();
    }
} public class MetaClass extends RubyClass {
    private IRubyObject attached;

    public MetaClass(Ruby runtime, RubyClass superClass) {
        super(runtime, superClass);
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public RubyClass getRealClass() {
        return superClass.getRealClass();
    }

    @Override
    public RubyClass makeMetaClass(RubyClass superClass) {
        RubyClass klass = super.makeMetaClass(superClass);
        klass.setSuperClass(getSuperClass().getRealClass().getMetaClass());
        return klass;
    }

    public IRubyObject getAttached() {
        return attached;
    }

    public void setAttached(IRubyObject attached) {
        this.attached = attached;
    }

    @Override
    public String toMethodPathString() {
        return attached.toString() + ".";
    }

    @Override
    public String toString() {
        return "<" + attached.toString() + ">";
    }
} public interface IRubyObject {
    public Ruby getRuntime();
    public RubyClass getMetaClass();
    public void setMetaClass(RubyClass metaClass);
    public RubyClass getSingletonClass();
    public RubyClass makeMetaClass(RubyClass superClass);
    public IRubyObject getInstVar(String name);
    public void setInstVar(String name, IRubyObject value);
    public boolean isNil();
    public boolean isInstanceOf(RubyModule klass);
    public boolean isKindOf(RubyModule klass);
    public Object getTag();
    public void setTag(Object tag);
} public class RubyObject implements IRubyObject {
    protected Ruby runtime;
    protected RubyClass metaClass;
    private Map<String, IRubyObject> instVars;
    private Object tag;

    protected RubyObject(Ruby runtime) {
        this(runtime, null);
    }

    public RubyObject(Ruby runtime, RubyClass metaClass) {
        this.runtime = runtime;
        this.metaClass = metaClass;
    }

    public Ruby getRuntime() {
        return runtime;
    }

    public RubyClass getMetaClass() {
        return metaClass;
    }
    
    public void setMetaClass(RubyClass metaClass) {
        this.metaClass = metaClass;
    }

    public RubyClass getSingletonClass() {
        RubyClass klass;
        if (getMetaClass().isSingleton() && ((MetaClass) getMetaClass()).getAttached() == this) {
            klass = getMetaClass();
        } else {
            klass = makeMetaClass(getMetaClass());
        }
        return klass;
    }

    public RubyClass makeMetaClass(RubyClass superClass) {
        MetaClass klass = new MetaClass(runtime, superClass);
        setMetaClass(klass);

        klass.setAttached(this);
        klass.setMetaClass(superClass.getRealClass().getMetaClass());

        superClass.addSubclass(klass);

        return klass;
    }

    public IRubyObject getInstVar(String name) {
        return instVars == null ? null : instVars.get(name);
    }

    public void setInstVar(String name, IRubyObject value) {
        if (instVars == null) {
            instVars = new HashMap<String, IRubyObject>();
        }
        instVars.put(name, value);
    }

    public boolean isNil() {
        return this == getRuntime().getNil();
    }

    public boolean isInstanceOf(RubyModule klass) {
        return runtime.isInstanceOf(this, klass);
    }

    public boolean isKindOf(RubyModule klass) {
        return runtime.isKindOf(this, klass);
    }

    public Object getTag() {
        return tag;
    }

    public void setTag(Object tag) {
        this.tag = tag;
    }

    @Override
    public String toString() {
        return "<obj:" + metaClass.toString() + ">";
    }
} public class LocalScope implements Scope {
    private RubyModule cref;
    private Map<String, IRubyObject> localVars;

    public LocalScope(RubyModule cref) {
        this.cref = cref;
        this.localVars = new HashMap<String, IRubyObject>();
    }

    public RubyModule getModule() {
        return cref;
    }

    public IRubyObject getValue(String name) {
        return localVars.get(name);
    }

    public void setValue(String name, IRubyObject value) {
        localVars.put(name, value);
    }

    @Override
    public int hashCode() {
        int code = 0;
        code = cref.hashCode();
        for (Map.Entry<String, IRubyObject> entry : localVars.entrySet()) {
            code ^= entry.getKey().hashCode();
            code <<= 4;
            code ^= entry.getValue().hashCode();
        }
        return code;
    }
} public class DynamicScope extends LocalScope {
    private Scope scope;
    private int snapshot;

    public DynamicScope(RubyModule cref, Scope scope) {
        super(cref);
        this.scope = scope;
        this.snapshot = scope.hashCode();
    }

    @Override
    public IRubyObject getValue(String name) {
        IRubyObject value = super.getValue(name);
        return value != null ? value : scope.getValue(name);
    }
    
    @Override
    public void setValue(String name, IRubyObject value) {
        if (scope.getValue(name) != null) {
            scope.setValue(name, value);
        } else {
            super.setValue(name, value);
        }
    }

    @Override
    public int hashCode() {
        return snapshot;
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }

        if (!(other instanceof DynamicScope)) {
            return false;
        }

        DynamicScope o = (DynamicScope) other;
        return snapshot == o.snapshot;
    }
} public class DynamicMethod {
    protected RubyModule cbase;
    protected Visibility visibility;
    protected ISourcePosition position;

    public DynamicMethod(RubyModule cbase, Visibility visibility, ISourcePosition position) {
        this.cbase = cbase;
        this.visibility = visibility;
        this.position = position;
    }

    public RubyModule getModule() {
        return cbase;
    }

    public Visibility getVisibility() {
        return visibility;
    }

    public void setVisibility(Visibility visibility) {
        this.visibility = visibility;
    }

    public ISourcePosition getPosition() {
        return position;
    }
} public class SourceLocation {
    private String file;
    private int line;
    
    public SourceLocation(String file, int line) {
        this.file = file;
        this.line = line;
    }

    public String getFile() {
        return file;
    }

    public int getLine() {
        return line;
    }

    @Override
    public String toString() {
        return file + ":" + line;
    }

    public static SourceLocation of(Node node) {
        ISourcePosition pos = node.getPosition();
        if (pos != null && pos != ISourcePosition.INVALID_POSITION) {
            return new SourceLocation(pos.getFile(), pos.getStartLine() + 1);
        } else {
            return null;
        }
    }
    
    public static SourceLocation of(Vertex vertex) {
        if (vertex != null && vertex.getNode() != null) {
            return of(vertex.getNode());
        } else {
            return null;
        }
    }
} public class InstanceFactory {
    private Ruby runtime;
    private Map<RubyClass, IRubyObject> instances;

    public InstanceFactory(Ruby runtime) {
        this.runtime = runtime;
        instances = new HashMap<RubyClass, IRubyObject>();
    }

    public IRubyObject newInstance(RubyClass klass) {
        ClassType classType = RuntimeHelper.getClassAnnotation(klass);
        if (classType != null && classType.isPolymorphic()) {
            return newPolymorphicInstance(klass, classType);
        } else {
            return newMonomorphicInstance(klass);
        }
    }

    public IRubyObject newPolymorphicInstance(RubyClass klass, ClassType type) {
        return new PolymorphicObject(runtime, klass);
    }

    public IRubyObject newMonomorphicInstance(RubyClass klass) {
        IRubyObject instance = instances.get(klass);
        if (instance == null) {
            instance = new RubyObject(runtime, klass);
            instances.put(klass, instance);
        }
        return instance;
    }
} public class VertexHolder extends RubyObject {
    private Vertex vertex;

    public VertexHolder(Ruby runtime, Vertex vertex) {
        super(runtime, runtime.getObject());
        this.vertex = vertex;
    }

    public Vertex getVertex() {
        return vertex;
    }

    @Override
    public String toString() {
        return vertex.toString();
    }

    @Override
    public int hashCode() {
        return vertex.hashCode();
    }

    @Override
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }

        if (!(object instanceof VertexHolder)) {
            return false;
        }

        VertexHolder o = (VertexHolder) object;
        return vertex.equals(o.vertex);
    }
} public class Array extends PolymorphicObject {
    protected Vertex[] elements;

    public Array(Ruby runtime, Vertex[] elements) {
        this(runtime, runtime.getArray(), elements);
    }

    public Array(Ruby runtime, RubyClass metaClass, Vertex[] elements) {
        super(runtime, metaClass);
        this.elements = elements;
    }

    public Array(Ruby runtime, RubyClass metaClass, Vertex[] elements, TypeVarMap tvmap) {
        super(runtime, metaClass, tvmap);
        this.elements = elements;
    }

    public Vertex getTypeVarVertex() {
        return getTypeVarMap().get(TypeVariable.valueOf("t"));
    }

    public void setTypeVarVertex(Vertex vertex) {
        getTypeVarMap().put(TypeVariable.valueOf("t"), vertex);
        if (elements != null) {
            for (Vertex e : elements) {
                vertex.copyTypeSet(e);
            }
        }
    }

    public Vertex getElement(int i) {
        if (elements == null) {
            return null;
        }
        if (i < 0) {
            i += elements.length;
        }
        if (0 <= i && i < elements.length) {
            return elements[i];
        }
        return null;
    }

    public Vertex[] getElements() {
        return elements;
    }

    public int length() {
        return elements != null ? elements.length : 0;
    }

    @Override
    public PolymorphicObject clone() {
        return new Array(runtime, metaClass, elements, getTypeVarMap().clone());
    }

    @Override
    public String toString() {
        if (isModified()) {
            // no longer static array
            return super.toString();
        }

        StringBuffer sb = new StringBuffer();
        String delim = "";
        sb.append('(');
        if (elements != null) {
            for (Vertex e : elements) {
                sb.append(delim);
                sb.append(e.toString());
                delim = ", ";
            }
        }
        sb.append(')');
        return sb.toString();
    }
} public class Hash extends Array {
    public static class Key {
        private Object realKey;
        private TypeSet typeSet;

        public Key(Object realKey, TypeSet typeSet) {
            this.realKey = realKey;
            this.typeSet = typeSet;
        }

        public Object getRealKey() {
            return realKey;
        }

        public TypeSet getTypeSet() {
            return typeSet;
        }
    }
    
    protected Key[] keys;

    public Hash(Ruby runtime, Vertex[] elements) {
        this(runtime, runtime.getHash(), elements);
    }

    public Hash(Ruby runtime, RubyClass metaClass, Vertex[] elements) {
        this(runtime, runtime.getHash(), elements, new TypeVarMap());
    }

    public Hash(Ruby runtime, RubyClass metaClass, Vertex[] elements, TypeVarMap tvmap) {
        super(runtime, metaClass, elements, tvmap);
        if (elements != null) {
            keys = new Key[elements.length / 2];
            for (int i = 0, j = 0; i < elements.length; i += 2, j++) {
                keys[j] = createVertexKey(elements[i]);
            }
        }
    }

    public Vertex get(Object key) {
        if (elements != null) {
            for (int i = 1, j = 0; i < elements.length; i += 2, j++) {
                if (key != null && key.equals(keys[j].getRealKey())) {
                    return elements[i];
                }
            }
        }
        return null;
    }

    public Vertex getKeyTypeVarVertex() {
        return getTypeVarMap().get(TypeVariable.valueOf("k"));
    }

    public void setKeyTypeVarVertex(Vertex vertex) {
        getTypeVarMap().put(TypeVariable.valueOf("k"), vertex);
        for (Key key : keys) {
            vertex.getTypeSet().addAll(key.getTypeSet());
        }
    }

    public Vertex getValueTypeVarVertex() {
        return getTypeVarMap().get(TypeVariable.valueOf("v"));
    }

    public void setValueTypeVarVertex(Vertex vertex) {
        getTypeVarMap().put(TypeVariable.valueOf("v"), vertex);
        if (elements != null) {
            for (int i = 1; i < elements.length; i += 2) {
                vertex.copyTypeSet(elements[i]);
            }
        }
    }

    @Override
    public PolymorphicObject clone() {
        return new Hash(runtime, metaClass, elements, getTypeVarMap().clone());
    }

    @Override
    public String toString() {
        if (isModified()) {
            // no longer static hash
            return super.toString();
        }

        StringBuffer sb = new StringBuffer();
        String delim = "";
        sb.append('{');
        if (elements != null) {
            for (int i = 0; i < elements.length; i++) {
                sb.append(delim);
                sb.append(keys[i].getRealKey());
                sb.append(" => ");
                sb.append(elements[i]);
                delim = ", ";
            }
        }
        sb.append('}');
        return sb.toString();
    }

    public static Object getRealKey(Node node) {
        Object realKey = null;
        if (node instanceof StrNode) {
            realKey = ((StrNode) node).getValue();
        } else if (node instanceof FixnumNode) {
            realKey = ((FixnumNode) node).getValue();
        } else if (node instanceof SymbolNode) {
            realKey = ":" + ((SymbolNode) node).getName();
        }
        return realKey;
    }

    public static Key createVertexKey(Vertex vertex) {
        return new Key(getRealKey(vertex.getNode()), vertex.getTypeSet());
    }
} public abstract class Method extends DynamicMethod {
    private String name;
    private Map<TemplateAttribute, Template> templates;
    private boolean templatesShared;
    private List<MethodType> annotations;

    public Method(RubyModule cbase, String name, Visibility visibility, ISourcePosition position) {
        super(cbase, visibility, position);
        this.name = name;
        templates = new HashMap<TemplateAttribute, Template>();
    }

    public String getName() {
        return name;
    }

    public Map<TemplateAttribute, Template> getTemplates() {
        return templates;
    }

    public void shareTemplates(Map<TemplateAttribute, Template> templates) {
        this.templates = templates;
        this.templatesShared = true;
    }
    
    public Template getTemplate(TemplateAttribute key) {
        return templates.get(key);
    }

    public void addTemplate(TemplateAttribute key, Template template) {
        templates.put(key, template);
    }

    public boolean isTemplatesShared() {
        return templatesShared;
    }

    public List<MethodType> getAnnotations() {
        return annotations;
    }

    public void setAnnotations(List<MethodType> annotations) {
        this.annotations = annotations;
    }

    public abstract Vertex call(Graph graph, Template template, IRubyObject receiver, IRubyObject[] args, Vertex[] argVertices, Block block);

    @Override
    public String toString() {
        return getModule().toMethodPathString() + name;
    }
} public class DefaultMethod extends Method {
    protected Node bodyNode, argsNode;

    public DefaultMethod(RubyModule cbase, String name, Node bodyNode, Node argsNode, Visibility visibility, ISourcePosition position) {
        super(cbase, name, visibility, position);
        this.bodyNode = bodyNode;
        this.argsNode = argsNode;
    }

    public Node getBodyNode() {
        return bodyNode;
    }

    public Node getArgsNode() {
        return argsNode;
    }

    public Vertex call(Graph graph, Template template, IRubyObject receiver, IRubyObject[] args, Vertex[] argVertices, Block block) {
        // FIXME more efficient way
        RuntimeHelper.argsAssign(graph, (ArgsNode) argsNode, argVertices, block);
        if (bodyNode != null) {
            return graph.createVertex(bodyNode);
        } else {
            return null;
        }
    }
} public class Proc extends RubyObject implements Block {
    private Node varNode;
    private Node bodyNode;
    private Frame frame;
    private Scope scope;
    private Set<YieldVertex> yields;

    public Proc(Ruby runtime, Node varNode, Node bodyNode, Frame frame, Scope scope) {
        super(runtime, runtime.getProc());
        this.varNode = varNode;
        this.bodyNode = bodyNode;
        this.frame = frame;
        this.scope = scope;
    }

    public Node getVarNode() {
        return varNode;
    }

    public Node getBodyNode() {
        return bodyNode;
    }

    public Frame getFrame() {
        return frame;
    }

    public Scope getScope() {
        return scope;
    }

    public Set<YieldVertex> getYields() {
        return yields != null ? yields : Collections.<YieldVertex>emptySet();
    }

    public boolean recordYield(YieldVertex vertex) {
        if (yields == null) {
            yields = new HashSet<YieldVertex>();
        }
        return yields.add(vertex);
    }

    @Override
    public int hashCode() {
        int code = 0;
        code ^= NodeUtil.nodeHashCode(varNode);
        code ^= NodeUtil.nodeHashCode(bodyNode);
        return code;
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }

        if (!(other instanceof Proc)) {
            return false;
        }

        Proc o = (Proc) other;
        return NodeUtil.nodeEquals(varNode, o.varNode)
            && NodeUtil.nodeEquals(bodyNode, o.bodyNode)
            && frame.equals(o.frame)
            && scope.equals(o.scope);
    }
} public class RuntimeHelper {
    private RuntimeHelper() {}

    public static Vertex assign(Graph graph, Node node) {
        return assign(graph, node);
    }

    public static Vertex assign(Graph graph, Node node, Vertex src) {
        switch (node.getNodeType()) {
        case LOCALASGNNODE:
            return localAssign(graph, (LocalAsgnNode) node, src);
        case DASGNNODE:
            return dynamicAssign(graph, (DAsgnNode) node, src);
        case INSTASGNNODE:
            return instanceAssign(graph, (InstAsgnNode) node, src);
        case CONSTDECLNODE:
            return constDeclaration(graph, (ConstDeclNode) node, src);
        case CLASSVARASGNNODE:
            return classVarAssign(graph, (ClassVarAsgnNode) node, src);
        case CLASSVARDECLNODE:
            return classVarDeclaration(graph, (ClassVarDeclNode) node, src);
        case ARGUMENTNODE:
        case RESTARG:
            return argumentAssign(graph, (ArgumentNode) node, src);
        }
        Logger.error("unknown assignable node: %s", node);
        return Graph.NULL_VERTEX;
    }
    
    public static Vertex localAssign(Graph graph, LocalAsgnNode node) {
        return localAssign(graph, node, null);
    }

    public static Vertex localAssign(Graph graph, LocalAsgnNode node, Vertex src) {
        Scope scope = graph.getRuntime().getContext().getCurrentScope();
        VertexHolder holder = (VertexHolder) scope.getValue(node.getName());
        if (src == null) {
            src = graph.createVertex(node.getValueNode());
        }
        if (holder == null) {
            holder = graph.createFreeVertexHolder();
            scope.setValue(node.getName(), holder);
        }
        graph.addEdgeAndPropagate(src, holder.getVertex());
        return src;
    }

    public static Vertex dynamicAssign(Graph graph, DAsgnNode node) {
        return dynamicAssign(graph, node, null);
    }

    public static Vertex dynamicAssign(Graph graph, DAsgnNode node, Vertex src) {
        Scope scope = graph.getRuntime().getContext().getCurrentScope();
        VertexHolder holder = (VertexHolder) scope.getValue(node.getName());
        if (src == null) {
            src = graph.createVertex(node.getValueNode());
        }
        if (holder == null) {
            holder = graph.createFreeVertexHolder();
            scope.setValue(node.getName(), holder);
        }
        graph.addEdgeAndPropagate(src, holder.getVertex());
        return src;
    }


    public static Vertex instanceAssign(Graph graph, InstAsgnNode node) {
        return instanceAssign(graph, node, null);
    }

    public static Vertex instanceAssign(Graph graph, InstAsgnNode node, Vertex src) {
        IRubyObject self = graph.getRuntime().getContext().getFrameSelf();
        VertexHolder holder = (VertexHolder) self.getInstVar(node.getName());
        if (src == null) {
            src = graph.createVertex(node.getValueNode());
        }
        if (holder == null) {
            holder = graph.createFreeVertexHolder();
            self.setInstVar(node.getName(), holder);
        }
        graph.addEdgeAndPropagate(src, holder.getVertex());
        return src;
    }


    public static Vertex instanceVariable(Graph graph, InstVarNode node) {
        IRubyObject self = graph.getRuntime().getContext().getFrameSelf();
        VertexHolder holder = (VertexHolder) self.getInstVar(node.getName());
        if (holder == null) {
            holder = graph.createFreeVertexHolder();
            self.setInstVar(node.getName(), holder);
        }
        return holder.getVertex();
    }

    public static Vertex classVarDeclaration(Graph graph, ClassVarDeclNode node) {
        return classVarDeclaration(graph, node, null);
    }
    
    public static Vertex classVarDeclaration(Graph graph, ClassVarDeclNode node, Vertex src) {
        RubyModule klass = graph.getRuntime().getContext().getFrameModule();
        if (src == null) {
            src = graph.createVertex(node.getValueNode());
        }
        VertexHolder holder = (VertexHolder) klass.getClassVar(node.getName());
        if (holder == null) {
            holder = graph.createFreeVertexHolder();
            klass.setClassVar(node.getName(), holder);
        }
        // Clear older types (performance issue)
        holder.getVertex().getTypeSet().clear();
        graph.addEdgeAndPropagate(src, holder.getVertex());
        return src;
    }

    public static Vertex classVarAssign(Graph graph, ClassVarAsgnNode node) {
        return classVarAssign(graph, node, null);
    }

    public static Vertex classVarAssign(Graph graph, ClassVarAsgnNode node, Vertex src) {
        RubyClass klass = (RubyClass) graph.getRuntime().getContext().getFrameModule();
        if (src == null) {
            src = graph.createVertex(node.getValueNode());
        }
        VertexHolder holder = (VertexHolder) klass.getClassVar(node.getName());
        if (holder == null) {
            holder = graph.createFreeVertexHolder();
            klass.setClassVar(node.getName(), holder);
        }
        // Clear older types (performance issue)
        holder.getVertex().getTypeSet().clear();
        graph.addEdgeAndPropagate(src, holder.getVertex());
        return src;
    }

    public static Vertex classVariable(Graph graph, ClassVarNode node) {
        RubyClass klass = (RubyClass) graph.getRuntime().getContext().getFrameModule();
        VertexHolder holder = (VertexHolder) klass.getClassVar(node.getName());
        if (holder == null) {
            holder = graph.createFreeVertexHolder();
            klass.setClassVar(node.getName(), holder);
        }
        return holder.getVertex();
    }

    public static Vertex constDeclaration(Graph graph, ConstDeclNode node) {
        return constDeclaration(graph, node, null);
    }
    
    public static Vertex constDeclaration(Graph graph, ConstDeclNode node, Vertex src) {
        RubyModule module = null;
        String name = null;
        INameNode constNode = (INameNode) node.getConstNode();
        if (constNode == null) {
            name = node.getName();
            module = graph.getRuntime().getContext().getFrameModule();
        } else if (constNode instanceof Colon2Node) {
            Node leftNode = ((Colon2Node) constNode).getLeftNode();

            Vertex v = graph.createVertex(leftNode);
            for (IRubyObject mod : v.getTypeSet()) {
                if (mod instanceof RubyModule) {
                    module = (RubyModule) mod;
                    break;
                }
            }
            name = constNode.getName();
        } else { // colon3
            module = graph.getRuntime().getObject();
            name = constNode.getName();
        }

        if (src == null) {
            src = graph.createVertex(node.getValueNode());
        }
        if (module != null && name != null) {
            module.setConstant(name, graph.createVertexHolder(src));
        }

        return src;
    }

    public static Vertex globalAssign(Graph graph, GlobalAsgnNode node) {
        return globalAssign(graph, node, null);
    }

    public static Vertex globalAssign(Graph graph, GlobalAsgnNode node, Vertex src) {
        Ruby runtime = graph.getRuntime();
        VertexHolder holder = (VertexHolder) runtime.getGlobalVar(node.getName());
        if (src == null) {
            src = graph.createVertex(node.getValueNode());
        }
        if (holder == null) {
            holder = graph.createFreeVertexHolder();
            runtime.setGlobalVar(node.getName(), holder);
        }
        // Clear older types (performance issue)
        holder.getVertex().getTypeSet().clear();
        graph.addEdgeAndPropagate(src, holder.getVertex());
        return src;
    }

    public static Vertex globalVariable(Graph graph, GlobalVarNode node) {
        Ruby runtime = graph.getRuntime();
        VertexHolder holder = (VertexHolder) runtime.getGlobalVar(node.getName());
        if (holder == null) {
            holder = graph.createFreeVertexHolder();
            runtime.setGlobalVar(node.getName(), holder);
        }
        return holder.getVertex();
    }

    public static void aliasGlobalVaraibles(Graph graph, String newName, String oldName) {
        Ruby runtime = graph.getRuntime();
        VertexHolder holder = (VertexHolder) runtime.getGlobalVar(oldName);
        if (holder == null) {
            holder = graph.createFreeVertexHolder();
            runtime.setGlobalVar(oldName, holder);
        }
        runtime.setGlobalVar(newName, holder); // no propagation ?
    }

    public static void blockAssign(Graph graph, BlockArgNode node, Block block) {
        Scope scope = graph.getRuntime().getContext().getCurrentScope();
        VertexHolder holder = (VertexHolder) scope.getValue(node.getName());
        if (holder == null) {
            holder = graph.createFreeVertexHolder();
            scope.setValue(node.getName(), holder);
        }
        holder.getVertex().addType((Proc) block);
    }

    public static void argsAssign(Graph graph, ArgsNode argsNode, Vertex[] args, Block block) {
        Scope scope = graph.getRuntime().getContext().getCurrentScope();
        ListNode pre = argsNode.getPre();
        ListNode post = argsNode.getPost();
        int preCount = argsNode.getPreCount();
        int postCount = argsNode.getPostCount();
        if (preCount > 0) {
            int size = pre.size();
            for (int i = 0; i < size; i++) {
                Node next = pre.get(i);
                Vertex arg = i < args.length ? args[i] : Graph.NULL_VERTEX;
                assign(graph, next, arg);
            }
        }
        if (postCount > 0) {
            int size = post.size();
            int argsLength = args.length;
            for (int i = 0; i < size; i++) {
                Node next = post.get(i);
                Vertex arg = argsLength - postCount + i < args.length
                    ? args[argsLength - postCount + i]
                    : Graph.NULL_VERTEX;
                if (next instanceof AssignableNode) {
                    assign(graph, next, arg);
                } else {
                    //scope.setValue(((INameNode) next).getName(), arg);
                    Logger.fixme("argsAssign2");
                }
            }
        }

        int index = preCount;
        ListNode optArgs = argsNode.getOptArgs();
        int restArg = argsNode.getRestArg();
        Node restArgNode = argsNode.getRestArgNode();
        int givenArgsCount = preCount;
        if (optArgs != null) {
            int j = 0;
            for (int i = preCount; i < args.length - postCount && j < optArgs.size(); i++, j++) {
                assign(graph, optArgs.get(j), args[i]);
                givenArgsCount++;
            }
            for (int i = 0; j < optArgs.size(); i++, j++) {
                graph.createVertex(optArgs.get(j));
            }
        }
        if (restArg >= 0) {
            int sizeOfRestArg = args.length - postCount - givenArgsCount;
            if (sizeOfRestArg <= 0) {
                assign(graph, restArgNode, createArrayVertex(graph, null, null));
            } else {
                assign(graph, restArgNode, createArrayVertex(graph, null, args, givenArgsCount, sizeOfRestArg));
            }
        }
        if (argsNode.getBlock() != null) {
            blockAssign(graph, argsNode.getBlock(), block);
        }
    }

    public static Vertex argumentAssign(Graph graph, ArgumentNode node, Vertex src) {
        Scope scope = graph.getRuntime().getContext().getCurrentScope();
        VertexHolder holder = graph.createFreeVertexHolder();
        scope.setValue(node.getName(), holder);
        if (src != null) {
            graph.addEdgeAndCopyTypeSet(src, holder.getVertex());
        }
        return src;
    }

    public static Vertex multipleAssign(Graph graph, MultipleAsgnNode node) {
        return multipleAssign(graph, node, (Vertex) null);
    }
    
    public static Vertex multipleAssign(Graph graph, MultipleAsgnNode node, Vertex src) {
        if (src == null) {
            src = graph.createVertex(node.getValueNode());
        }
        MultipleAsgnVertex vertex = new MultipleAsgnVertex(node, src);
        graph.addEdgeAndPropagate(src, vertex);
        return src;
    }

    public static void multipleAssign(Graph graph, MultipleAsgnNode node, IRubyObject object) {
        boolean isArray = object instanceof Array;
        Array array = null;
        Vertex element = Graph.NULL_VERTEX;

        if (isArray) {
            array = (Array) object;
            if (array.isModified()) {
                return;
            }
        } else {
            TypeVarMap tvmap = getTypeVarMap(object);
            TypeVariable var = TypeVariable.valueOf("t");
            if (tvmap != null && tvmap.containsKey(var)) {
                element = tvmap.get(var);
            }
        }
        
        int valLen = array != null ? array.length() : 256; // magic number
        int varLen = node.getHeadNode() == null ? 0 : node.getHeadNode().size();

        int j = 0;
        for (; j < valLen && j < varLen; j++) {
            assign(graph, node.getHeadNode().get(j), array != null ? array.getElement(j) : element);
        }

        Node argsNode = node.getArgsNode();
        if (argsNode != null) {
            if (argsNode.getNodeType() == NodeType.STARNODE) {
                // no check for '*'
            } else if (varLen < valLen && array != null) {
                assign(graph, argsNode, createArrayVertex(graph, null, array.getElements(), varLen, valLen - varLen));
            } else {
                Vertex[] elements = null;
                if (element != null) {
                    elements = new Vertex[] { element };
                }
                assign(graph, argsNode, createArrayVertex(graph, null, elements));
            }
        }

        while (j < varLen) {
            assign(graph, node.getHeadNode().get(j++), element);
        }
    }

    public static Vertex[] setupCallArgs(Graph graph, Node argsNode) {
        List<Vertex> args = new ArrayList<Vertex>();
        if (argsNode != null) {
            switch (argsNode.getNodeType()) {
            case ARGSCATNODE:
            case SPLATNODE: {
                Vertex arrayVertex = graph.createVertex(argsNode);
                for (IRubyObject object : arrayVertex.getTypeSet()) {
                    if (object instanceof Array) {
                        Array array = (Array) object;
                        if (array.getElements() != null) {
                            for (Vertex element : array.getElements()) {
                                args.add(element);
                            }
                        }
                    }
                }
                break;
            }
            default:
                for (Node arg : argsNode.childNodes()) {
                    args.add(graph.createVertex(arg));
                }
            }
        }
        return args.isEmpty() ? null : args.toArray(new Vertex[0]);
    }

    public static Block setupCallBlock(Graph graph, Node iterNode) {
        Context context = graph.getRuntime().getContext();
        Block block = null;
        if (iterNode != null) {
            switch (iterNode.getNodeType()) {
            case ITERNODE: {
                IterNode inode = (IterNode) iterNode;
                DynamicScope scope = new DynamicScope(context.getCurrentScope().getModule(), context.getCurrentScope());
                block = new Proc(graph.getRuntime(), inode.getVarNode(), inode.getBodyNode(), context.getCurrentFrame(), scope);
                break;
            }
            case BLOCKPASSNODE:
                block = context.getFrameBlock();
                break;
            }
        }
        return block;
    }
        
    public static Vertex call(Graph graph, CallVertex vertex) {
        return call(graph, vertex, false);
    }

    public static Vertex callSuper(Graph graph, CallVertex vertex) {
        return call(graph, vertex, true);
    }
    
    private static Vertex call(Graph graph, CallVertex vertex, boolean callSuper) {
        if (vertex.isApplicable()) {
            String name = vertex.getName();
            TypeSet receivers = vertex.getReceiverVertex().getTypeSet();
            Block block = vertex.getBlock();
            boolean noReturn = false;
            Vertex[] argVertices = vertex.getArgVertices();
            SpecialMethod.Result prevResult = null;
            TypeSet accumulator = new TypeSet();

            while (!receivers.isEmpty()) {
                SpecialMethod.Result result = new SpecialMethod.Result(prevResult, accumulator);
                prevResult = result;
                SpecialMethod spec = graph.getSpecialMethod(name);
                if (spec != null) {
                    spec.call(graph.getRuntime(), receivers, argVertices, block, result);
                    if (result.getResultTypeSet() != null) {
                        accumulator.addAll(result.getResultTypeSet());
                    }
                    if (result.hasPrivateVisibility()) {
                        vertex.setPrivateVisibility(true);
                    }
                    if (result.isNeverCallAgain()) {
                        vertex.cutout();
                    } else if (!result.isCallNextMethod()) {
                        break;
                    }
                }

                List<Collection<IRubyObject>> args = new ArrayList<Collection<IRubyObject>>();
                if (vertex.getArgVertices() != null) {
                    for (Vertex v : argVertices) {
                        args.add(v.getTypeSet());
                    }
                }

                //Logger.debug("try to apply template: %s", name);
                List<TemplateAttribute> attrs = generateTemplateAttributes(receivers, args, block);
                for (TemplateAttribute attr : attrs) {
                    Vertex returnVertex = applyTemplateAttribute(graph, vertex, name, attr, callSuper);
                    if (returnVertex != null && !noReturn) {
                        accumulator.addAll(returnVertex.getTypeSet());
                    }
                }
                if (result.isNextMethodChange()) {
                    name = result.getNextMethodName();
                    receivers = result.getNextMethodReceivers();
                    block = result.getNextMethodBlock();
                    noReturn = result.isNextMethodNoReturn();
                } else {
                    break;
                }
            }

            vertex.getTypeSet().addAll(accumulator);
        }
        return vertex;
    }

    public static void methodPartialUpdate(Graph graph, MethodDefNode node, DynamicMethod newMethod, DynamicMethod oldMethod, IRubyObject receiver) {
        if (newMethod instanceof DefaultMethod && oldMethod instanceof DefaultMethod) {
            DefaultMethod newmeth = (DefaultMethod) newMethod;
            DefaultMethod oldmeth = (DefaultMethod) oldMethod;
            NodeDiff nodeDiff = graph.getNodeDiff();

            if (nodeDiff != null
                && nodeDiff.noDiff(node.getArgsNode(), oldmeth.getArgsNode())
                && nodeDiff.noDiff(node.getBodyNode(), oldmeth.getBodyNode())) { // XXX nested class, defn
                // FIXME annotation diff
                newmeth.shareTemplates(oldmeth.getTemplates());
            } else {
                Logger.debug(SourceLocation.of(node), "templates not shared: %s", newmeth);
            }
        }
    }

    public static void classPartialUpdate(Graph graph, RubyModule klass, Node bodyNode) {
        NodeDiff nodeDiff = graph.getNodeDiff();

        if (bodyNode != null) {
            ClassTag oldTag = getClassTag(klass);
            if (nodeDiff != null && oldTag != null) {
                List<Node> partialDiff = nodeDiff.diff(bodyNode, oldTag.getBodyNode());
                if (partialDiff != null) {
                    Logger.debug(SourceLocation.of(bodyNode), "class partial update: %s %s", klass, partialDiff.size());
                    for (Node dirty : partialDiff) {
                        graph.createVertex(dirty);
                    }
                } else {
                    graph.createVertex(bodyNode);
                }
            } else {
                graph.createVertex(bodyNode);
            }
        }
    }

    public static void dummyCall(Graph graph, MethodDefNode node, Method method, IRubyObject receiver) {
        if (node.getBodyNode() != null) {
            Context context = graph.getRuntime().getContext();
            context.pushFrame(context.getFrameModule(), node.getName(), receiver, null, Visibility.PUBLIC);
            context.pushScope(new LocalScope(method.getModule()));
            graph.createVertex(node.getBodyNode());
            context.popScope();
            context.popFrame();
        }
        Logger.debug(SourceLocation.of(node), "dummy call: %s", method);
    }

    public static void dummyCallForTemplates(Graph graph, MethodDefNode node, Method method, Map<TemplateAttribute, Template> templates) {
        String name = node.getName();
        for (TemplateAttribute attr : templates.keySet()) {
            createTemplate(graph, null, name, method, attr);
        }
        Logger.debug(SourceLocation.of(node), "template dummy call: %s", method);
    }

    private static Vertex applyTemplateAttribute(Graph graph, CallVertex vertex, String name, TemplateAttribute attr, boolean callSuper) {
        IRubyObject receiver = attr.getReceiver();
        IRubyObject[] args = attr.getArgs();
        RubyClass receiverType = null;
        if (callSuper) {
            RubyModule module = graph.getRuntime().getContext().getFrameModule();
            if (module instanceof RubyClass) {
                RubyClass klass = (RubyClass) module;
                receiverType = klass.getSuperClass();
            } else {
                Logger.error("Cannot call super in module");
                return null;
            }
        } else if (receiver != null) {
            receiverType = receiver.getMetaClass();
        }
        if (receiverType != null) {
            Method method = (Method) receiverType.searchMethod(name);
            if (method != null
                && (method.getVisibility() != Visibility.PRIVATE
                    || vertex.hasPrivateVisibility())) {
                Template template = method.getTemplate(attr);
                if (template == null) {
                    template = createTemplate(graph, vertex, name, method, attr);
                    Logger.debug(SourceLocation.of(vertex), "template created: %s", method);
                } else {
                    template.reproduceSideEffect(graph, receiver, args, vertex.getBlock());
                    Logger.debug(SourceLocation.of(vertex), "template reused: %s", method);
                }
                return template.getReturnVertex();
            }
        }
        return null;
    }

    private static Template createTemplate(Graph graph, CallVertex vertex, String name, Method method, TemplateAttribute attr) {
        IRubyObject receiver = attr.getReceiver();
        IRubyObject[] args = attr.getArgs();
        Vertex[] argVertices = new Vertex[args.length];
        for (int i = 0; i < argVertices.length; i++) {
            argVertices[i] = graph.createFreeSingleTypeVertex(args[i]);
        }

        Ruby runtime = graph.getRuntime();
        Context context = runtime.getContext();

        Block block = attr.getBlock();
        Scope scope = new LocalScope(method.getModule());
        context.pushFrame(context.getFrameModule(), name, receiver, block, Visibility.PUBLIC);
        context.pushScope(scope);

        Template template = new Template(method, context.getCurrentFrame(), scope, attr);
        method.addTemplate(attr, template);

        Vertex returnVertex = template.getReturnVertex();
        setFrameTemplate(context.getCurrentFrame(), template);

        AnnotationResolver.Result result = AnnotationHelper.resolveMethodAnnotation(graph, template);
        if (result == AnnotationResolver.Result.UNRESOLVED) {
            Logger.warn(SourceLocation.of(vertex), "annotation unmatched: %s", method);
        }

        Vertex ret = method.call(graph, template, receiver, args, argVertices, block);
        if (ret != null && result != AnnotationResolver.Result.RESOLVED) {
            graph.addEdgeAndCopyTypeSet(ret, returnVertex);
        }

        context.popScope();
        context.popFrame();

        return template;
    }

    private static List<TemplateAttribute> generateTemplateAttributes(Collection<IRubyObject> receivers, List<Collection<IRubyObject>> args, Block block) {
        int size = 1;
        for (Collection<IRubyObject> c : args) {
            size *= c.size();
        }
        if (size == 0) {
            return Collections.emptyList();
        } else if (size >= 128) {
            Logger.warn("too big application: %s", size);
            return Collections.emptyList();
        }

        int unit = size;
        List<TemplateAttribute> result = new ArrayList<TemplateAttribute>(size);
        for (int i = 0; i < size; i++) {
            result.add(new TemplateAttribute(new IRubyObject[args.size()]));
        }

        for (int i = 0; i < args.size(); i++) {
            Collection<IRubyObject> c = args.get(i);
            Iterator<IRubyObject> ite = c.iterator();
            int k = 0, n = c.size();
            int newUnit = unit / n;
            IRubyObject v = ite.next();
            for (int j = 0; j < size; j++) {
                result.get(j).setArg(i, v);
                if (++k == newUnit) {
                    k = 0;
                    if (!ite.hasNext()) {
                        ite = c.iterator();
                    }
                    v = ite.next();
                }
            }
            unit = newUnit;
        }

        // handle receiver polymorphic and data polymorphic
        int resultSize = result.size();
        for (int i = 0; i < resultSize; i++) {
            TemplateAttribute base = result.get(i);
            boolean first = true;
            for (IRubyObject recv : receivers) {
                base.setReceiver(recv);
                base.setBlock(block);
                if (recv instanceof PolymorphicObject) {
                    for (PolymorphicObject mrecv : ((PolymorphicObject) recv).generateMonomorphicObjects()) {
                        base.setReceiver(mrecv);
                        if (!first) {
                            result.add(base);
                        }
                        first = false;
                        base = base.clone();
                    }
                } else {
                    if (!first) {
                        result.add(base);
                    }
                    first = false;
                    base = base.clone();
                }
            }
        }

        // handle arguments data polymorphic
        for (int i = 0; i < args.size(); i++) {
            resultSize = result.size();
            for (int j = 0; j < resultSize; j++) {
                TemplateAttribute base = result.get(j);
                IRubyObject arg = base.getArg(i);
                if (arg instanceof PolymorphicObject) {
                    boolean first = true;
                    for (PolymorphicObject marg : ((PolymorphicObject) arg).generateMonomorphicObjects()) {
                        base.setArg(i, marg);
                        if (!first) {
                            result.add(base);
                        }
                        first = false;
                        base = base.clone();
                    }
                }
            }
        }

        return result;
    }

    public static Vertex yield(Graph graph, Block block, IRubyObject arg, boolean expanded, Vertex returnVertex) {
        return yield(graph, block, Arrays.asList(arg), expanded, returnVertex);
    }

    public static Vertex yield(Graph graph, Block block, IRubyObject[] args, boolean expanded, Vertex returnVertex) {
        return yield(graph, block, Arrays.asList(args), expanded, returnVertex);
    }
    
    public static Vertex yield(Graph graph, Block block, Collection<IRubyObject> args, boolean expanded, Vertex returnVertex) {
        if (block == null) {
            return Graph.NULL_VERTEX;
        }
        Ruby runtime = graph.getRuntime();
        Context context = runtime.getContext();
        
        Node varNode = block.getVarNode();
        boolean noargblock = false;
        MultipleAsgnNode masgn = null;
        int preCount = 0;
        boolean isRest = false;
        Node rest = null;
        ListNode pre = null;
        Vertex vertex = graph.createFreeVertex();

        if (varNode == null || varNode instanceof ZeroArgNode) {
            noargblock = true;
        } else if (varNode instanceof MultipleAsgnNode) {
            masgn = (MultipleAsgnNode) varNode;
            preCount = masgn.getPreCount();
            isRest = masgn.getRest() != null;
            rest = masgn.getRest();
            pre = masgn.getPre();
        }
        
        if (args != null && !args.isEmpty()) {
            for (IRubyObject value : args) {
                pushLoopFrame(context, block.getFrame(), returnVertex, vertex);
                context.pushScope(block.getScope());

                if (noargblock) {}
                else if (masgn != null) {
                    Array array;
                    if (!expanded) {
                        // FIXME to_ary
                        if (value instanceof Array) {
                            array = (Array) value;
                        } else {
                            array = createArray(graph, new Vertex[] { graph.createFreeSingleTypeVertex(value) });
                        }
                    } else {
                        if (value instanceof Array) {
                            array = (Array) value;
                        } else {
                            array = createArray(graph, new Vertex[] { graph.createFreeSingleTypeVertex(value) });
                        }
                    }
                    multipleAssign(graph, masgn, array);
                } else {
                    assign(graph, varNode, graph.createFreeSingleTypeVertex(value));
                }

                if (block.getBodyNode() != null) {
                    Vertex v = graph.createVertex(block.getBodyNode());
                    graph.addEdgeAndPropagate(v, vertex);
                }

                context.popScope();
                popLoopFrame(context);
            }
        } else {
            pushLoopFrame(context, block.getFrame(), returnVertex, vertex);
            context.pushScope(block.getScope());

            if (block.getBodyNode() != null) {
                Vertex v = graph.createVertex(block.getBodyNode());
                graph.addEdgeAndPropagate(v, vertex);
            }
            
            context.popScope();
            popLoopFrame(context);
        }

        return vertex;
    }

    public static Vertex yield(Graph graph, YieldVertex vertex) {
        Template template = vertex.getTemplate();
        if (template == null) {
            return vertex;
        }

        Proc block = (Proc) vertex.getBlock();
        Vertex argsVertex = vertex.getArgsVertex();
        Vertex returnVertex = yield(graph, block, (argsVertex != null ? argsVertex.getTypeSet() : TypeSet.EMPTY), vertex.getExpandArguments(), template.getReturnVertex());
        if (returnVertex != null) {
            graph.addEdgeAndCopyTypeSet(returnVertex, vertex);
        }
        
        if (block != null) {
            block.recordYield(vertex);
        }
        return vertex;
    }

    public static void splatValue(Graph graph, SplatVertex vertex) {
        vertex.getTypeSet().addAll(arrayValue(graph, vertex.getValueVertex()));
    }

    public static void toAryValue(Graph graph, ToAryVertex vertex) {
        vertex.getTypeSet().addAll(arrayValue(graph, vertex.getValueVertex()));
    }

    public static TypeSet arrayValue(Graph graph, Vertex vertex) {
        Ruby runtime = graph.getRuntime();
        TypeSet typeSet = new TypeSet();
        for (IRubyObject object : vertex.getTypeSet()) {
            if (object.isKindOf(runtime.getArray())) {
                typeSet.add(object);
            } else {
                CallVertex callVertex = new CallVertex(vertex.getNode(), "to_a", vertex, null, null);
                for (IRubyObject array : call(graph, callVertex).getTypeSet()) {
                    if (array.isKindOf(runtime.getArray())) {
                        typeSet.add(array);
                    } else {
                        Logger.warn("to_a should be return Array");
                    }
                }
            }
        }
        return typeSet;
    }

    public static void aValueSplat(Graph graph, SValueVertex vertex) {
        for (IRubyObject object : vertex.getValueVertex().getTypeSet()) {
            if (object.isKindOf(graph.getRuntime().getArray())) {
                if (object instanceof Array) {
                    Array array = (Array) object;
                    if (!array.isModified()) {
                        if (array.length() == 1) {
                            vertex.copyTypeSet(array.getElement(0));
                        } else {
                            vertex.addType(object);
                        }
                        continue;
                    }
                }
                TypeVariable var = TypeVariable.valueOf("t");
                TypeVarMap tvmap = getTypeVarMap(object);
                if (tvmap != null && tvmap.containsKey(var)) {
                    vertex.copyTypeSet(tvmap.get(var));
                }
            }
        }
    }

    public static RubyModule getNamespace(Graph graph, Colon3Node node) {
        if (node instanceof Colon2ImplicitNode) {
            return graph.getRuntime().getContext().getFrameModule();
        } else if (node instanceof Colon2Node) {
            IRubyObject object = graph.createVertex(((Colon2Node) node).getLeftNode()).singleType();
            if (object instanceof RubyModule) {
                return (RubyModule) object;
            } else {
                return null;
            }
        } else if (node instanceof Colon3Node) {
            return graph.getRuntime().getObject();
        }
        Logger.fixme("Namespace unresolved: %s", node);
        return null;
    }

    public static Template getFrameTemplate(Frame frame) {
        Object tag;
        while (frame != null) {
            tag = frame.getTag();
            if (tag instanceof Template) {
                return (Template) tag;
            }
            frame = frame.getPrevious();
        }
        return null;
    }

    public static void setFrameTemplate(Frame frame, Template template) {
        frame.setTag(template);
    }

    public static LoopTag getFrameLoopTag(Frame frame) {
        Object tag = frame.getTag();
        if (tag instanceof LoopTag) {
            return (LoopTag) tag;
        } else {
            return null;
        }
    }

    public static void pushLoopFrame(Context context, Vertex returnVertex, Vertex yieldVertex) {
        pushLoopFrame(context, context.getCurrentFrame(), returnVertex, yieldVertex);
    }
    
    public static void pushLoopFrame(Context context, Frame prev, Vertex returnVertex, Vertex yieldVertex) {
        Frame frame = context.pushFrame(prev.getModule(), prev.getName(), prev.getSelf(), prev.getBlock(), prev.getVisibility());
        frame.setTag(new LoopTag(returnVertex, yieldVertex));
    }

    public static void popLoopFrame(Context context) {
        context.popFrame();
    }

    public static ClassTag getClassTag(RubyModule klass) {
        if (klass instanceof MetaClass) {
            MetaClass metaClass = (MetaClass) klass;
            if (metaClass.getAttached() instanceof RubyModule) {
                klass = (RubyModule) metaClass.getAttached();
            }
        }
        if (klass.getTag() instanceof ClassTag) {
            return (ClassTag) klass.getTag();
        } else {
            return null;
        }
    }

    public static ClassType getClassAnnotation(RubyModule klass) {
        ClassTag tag = getClassTag(klass);
        return tag != null ? tag.getType() : null;
    }
    
    public static void setClassTag(RubyModule klass, Node node, List<TypeAnnotation> annotations) {
        if (getClassAnnotation(klass) == null) {
            ClassType type = null;
            for (TypeAnnotation annot : annotations) {
                if (annot instanceof ClassType) {
                    type = (ClassType) annot;
                    break;
                }
            }
            klass.setTag(new ClassTag(node, type));
        }
    }

    public static void setMethodTag(Method method, /*unused*/Node node, List<TypeAnnotation> annotations) {
        if (!annotations.isEmpty()) {
            List<MethodType> types = new ArrayList<MethodType>();
            for (TypeAnnotation annot : annotations) {
                if (annot instanceof MethodType) {
                    types.add((MethodType) annot);
                }
            }
            method.setAnnotations(types);
        }
    }

    public static ClassTag getEnclosingClassTag(RubyModule module) {
        ClassTag tag;
        while (module != null) {
            tag = getClassTag(module);
            if (tag != null) {
                return tag;
            }
            module = module.getParent();
        }
        return null;
    }

    public static ClassType getEnclosingClassAnnotation(RubyModule module) {
        ClassTag tag = getEnclosingClassTag(module);
        return tag != null ? tag.getType() : null;
    }

    public static TypeVarMap getTypeVarMap(IRubyObject object) {
        if (object.getTag() instanceof TypeVarMap) {
            return (TypeVarMap) object.getTag();
        } else {
            return null;
        }
    }

    public static Array createArray(Graph graph, Vertex[] elements) {
        Array array = new Array(graph.getRuntime(), elements);
        TypeVarVertex t = new TypeVarVertex(array);
        array.setTypeVarVertex(t);
        return array;
    }

    public static Vertex createArrayVertex(Graph graph, Node node, Vertex[] elements) {
        Array array = createArray(graph, elements);
        Vertex vertex = new PassThroughVertex(node);
        vertex.addType(array);
        array.getTypeVarVertex().addEdge(vertex);
        return vertex;
    }

    public static Vertex createArrayVertex(Graph graph, Node node, Vertex[] elements, int offset, int length) {
        Vertex[] newElements = new Vertex[length];
        System.arraycopy(elements, offset, newElements, 0, length);
        return createArrayVertex(graph, node, newElements);
    }

    public static Hash createHash(Graph graph, Vertex[] elements) {
        Hash hash = new Hash(graph.getRuntime(), elements);
        TypeVarVertex k = new TypeVarVertex(hash);
        TypeVarVertex v = new TypeVarVertex(hash);
        hash.setKeyTypeVarVertex(k);
        hash.setValueTypeVarVertex(v);
        return hash;
    }


    public static Vertex createHashVertex(Graph graph, Node node, Vertex[] elements) {
        Hash hash = createHash(graph, elements);
        Vertex vertex = new PassThroughVertex(node);
        vertex.addType(hash);
        hash.getKeyTypeVarVertex().addEdge(vertex);
        hash.getValueTypeVarVertex().addEdge(vertex);
        return vertex;
    }

    public static Vertex[] toVertices(Graph graph, ListNode node) {
        Vertex[] vertices = new Vertex[node.size()];
        for (int i = 0; i < vertices.length; i++) {
            vertices[i] = graph.createVertex(node.get(i));
        }
        return vertices;
    }

    public static void setMethodsVisibility(Graph graph, TypeSet receivers, Vertex[] args, Visibility visibility) {
        if (args == null || args.length == 0) {
            // FIXME module check
            graph.getRuntime().getContext().getCurrentFrame().setVisibility(visibility);
        } else {
            for (IRubyObject receiver : receivers) {
                if (receiver.isKindOf(graph.getRuntime().getModule())) {
                    RubyModule module = (RubyModule) receiver;
                    for (Vertex arg : args) {
                        String name = Vertex.getStringOrSymbol(arg);
                        if (name != null) {
                            DynamicMethod method = module.getMethod(name);
                            if (method != null) {
                                method.setVisibility(visibility);
                                if (visibility == Visibility.MODULE_FUNCTION) {
                                    module.getSingletonClass().addMethod(name, method);
                                }
                            }
                        }                        
                    }
                }
            }
        }
    }

    public static void defineAttrs(Graph graph, TypeSet receivers, Vertex[] args, boolean reader, boolean writer) {
        if (args != null && args.length > 0) {
            for (IRubyObject receiver : receivers) {
                if (receiver.isKindOf(graph.getRuntime().getModule())) {
                    for (Vertex arg : args) {
                        String name = Vertex.getStringOrSymbol(arg);
                        if (name != null) {
                            if (reader) {
                                defineAttrReader(graph, name);
                            }
                            if (writer) {
                                defineAttrWriter(graph, name);
                            }
                        }
                    }
                }
            }
        }
    }

    public static void defineAttrReader(Graph graph, String name) {
        ISourcePosition pos = new SimpleSourcePosition("(generated)", 0);
        graph.createVertex(new DefnNode(pos,
                                        new ArgumentNode(pos, name),
                                        new ArgsNoArgNode(pos),
                                        new LocalStaticScope(null),
                                        new InstVarNode(pos, "@" + name)));
    }

    public static void defineAttrWriter(Graph graph, String name) {
        ISourcePosition pos = new SimpleSourcePosition("(generated)", 0);
        graph.createVertex(new DefnNode(pos,
                                        new ArgumentNode(pos, name + "="),
                                        new ArgsPreOneArgNode(pos, new ListNode(null, new ArgumentNode(null, name))),
                                        new LocalStaticScope(null),
                                        new InstAsgnNode(pos, "@" + name, new LocalVarNode(null, 0, name))));
    }
} public class TypeVarMap extends HashMap<TypeVariable, Vertex> {
    private static final long serialVersionUID = 0L;

    public TypeVarMap() {
        super();
    }

    public TypeVarMap(int initialCapacity) {
        super(initialCapacity);
    }

    public TypeVarMap(TypeVarMap other) {
        super(other);
    }

    public TypeVarMap[] generateTuples() {
        TypeVariable[] keys = keySet().toArray(new TypeVariable[0]);
        Vertex[] values = values().toArray(new Vertex[0]);

        int size = 1;
        for (Vertex vertex : values) {
            size *= vertex.getTypeSet().size();
        }
        if (size == 0) {
            return new TypeVarMap[0];
        }

        int unit = size;
        TypeVarMap[] result = new TypeVarMap[size];
        for (int i = 0; i < size; i++) {
            result[i] = new TypeVarMap(size());
        }

        for (int i = 0; i < values.length; i++) {
            TypeSet typeSet = values[i].getTypeSet();
            Iterator<IRubyObject> ite = typeSet.iterator();
            int k = 0, n = typeSet.size();
            int newUnit = unit / n;
            IRubyObject v = ite.next();
            for (int j = 0; j < size; j++) {
                Vertex vertex = new Vertex();
                vertex.addType(v);
                result[j].put(keys[i], vertex);
                if (++k == newUnit) {
                    k = 0;
                    if (!ite.hasNext()) {
                        ite = typeSet.iterator();
                    }
                    v = ite.next();
                }
            }
            unit = newUnit;
        }

        return result;
    }

    public TypeVarMap clone() {
        TypeVarMap clone = new TypeVarMap();
        for (Map.Entry<TypeVariable, Vertex> entry : entrySet()) {
            clone.put(entry.getKey(), new Vertex(null, new TypeSet(entry.getValue().getTypeSet())));
        }
        return clone;
    }
} public class Vertex {
    protected Node node;
    protected TypeSet typeSet;
    protected List<Vertex> edges;

    public Vertex() {
        this(null);
    }

    public Vertex(Node node) {
        this(node, new TypeSet());
    }

    public Vertex(Node node, TypeSet typeSet) {
        this.node = node;
        this.typeSet = typeSet;
        edges = new ArrayList<Vertex>();
    }

    public Node getNode() {
        return node;
    }

    public TypeSet getTypeSet() {
        return typeSet;
    }

    public List<Vertex> getEdges() {
        return edges;
    }

    public boolean isEmpty() {
        return typeSet.isEmpty();
    }

    public void addType(IRubyObject type) {
        typeSet.add(type);
    }

    public void addTypeSet(TypeSet typeSet) {
        this.typeSet.addAll(typeSet);
    }
    
    public void copyTypeSet(Vertex other) {
        typeSet.addAll(other.getTypeSet());
    }

    public IRubyObject singleType() {
        if (typeSet.size() != 1) {
            return null;
        }
        return typeSet.iterator().next();
    }

    public void addEdge(Vertex dest) {
        edges.add(dest);
    }

    public void removeEdge(Vertex dest) {
        edges.remove(dest);
    }

    public boolean accept(Propagation propagation, Vertex src) {
        return propagation.getGraph().propagateVertex(propagation, this, src);
    }

    public boolean isFree() {
        return node == null;
    }

    @Override
    public String toString() {
        return typeSet.toString();
    }

    public static String getName(Vertex vertex) {
        Node node = vertex.getNode();
        if (node instanceof INameNode) {
            return ((INameNode) node).getName();
        } else {
            return null;
        }
    }

    public static Integer getFixnum(Vertex vertex) {
        Node node = vertex.getNode();
        if (node instanceof FixnumNode) {
            return Integer.valueOf((int) ((FixnumNode) node).getValue());
        } else {
            return null;
        }
    }

    public static String getString(Vertex vertex) {
        Node node = vertex.getNode();
        if (node instanceof StrNode) {
            return ((StrNode) node).getValue().toString();
        } else {
            return null;
        }
    }

    public static String getSymbol(Vertex vertex) {
        Node node = vertex.getNode();
        if (node instanceof SymbolNode) {
            return ((SymbolNode) node).getName();
        } else {
            return null;
        }
    }

    public static String getStringOrSymbol(Vertex vertex) {
        String value = getString(vertex);
        return value != null ? value : getSymbol(vertex);
    }
} public class CallVertex extends Vertex {
    private String name;
    private Vertex receiverVertex;
    private Vertex[] argVertices;
    private Block block;
    private boolean privateVisibility;

    public CallVertex(Node node, Vertex receiverVertex, Vertex[] argVertices, Block block) {
        this(node, null, receiverVertex, argVertices, block);
    }

    public CallVertex(Node node, String name, Vertex receiverVertex, Vertex[] argVertices, Block block) {
        super(node);
        this.name = name;
        this.receiverVertex = receiverVertex;
        this.argVertices = argVertices;
        this.block = block;
        this.privateVisibility = false;
        receiverVertex.addEdge(this);
        if (argVertices != null) {
            for (Vertex v : argVertices) {
                v.addEdge(this);
            }
        }
    }

    public void cutout() {
        if (argVertices != null) {
            for (Vertex v : argVertices) {
                v.removeEdge(this);
            }
        }
        receiverVertex.removeEdge(this);
    }

    public String getName() {
        return name == null ? ((INameNode) node).getName() : name;
    }

    public Vertex getReceiverVertex() {
        return receiverVertex;
    }

    public Vertex[] getArgVertices() {
        return argVertices;
    }

    public Block getBlock() {
        return block;
    }

    public boolean hasPrivateVisibility() {
        return privateVisibility;
    }

    public void setPrivateVisibility(boolean privateVisibility) {
        this.privateVisibility = privateVisibility;
    }

    public boolean isApplicable() {
        if (receiverVertex == null || receiverVertex.isEmpty()) {
            return false;
        }
        
        boolean applicable = true;
        if (argVertices != null) {
            for (Vertex v : argVertices) {
                if (v.isEmpty()) {
                    applicable = false;
                    break;
                }
            }
        }
        return applicable;
    }

    @Override
    public boolean accept(Propagation propagation, Vertex src) {
        return propagation.getGraph().propagateCallVertex(propagation, this, src);
    }
} public class YieldVertex extends Vertex {
    private Template template;
    private Block block;
    private Vertex argsVertex;
    private boolean expandArguments;

    public YieldVertex(Node node, Template template, Block block, Vertex argsVertex, boolean expandArguments) {
        super(node);
        this.template = template;
        this.block = block;
        this.argsVertex = argsVertex;
        this.expandArguments = expandArguments;
        if (argsVertex != null) {
            argsVertex.addEdge(this);
        }
    }

    public Template getTemplate() {
        return template;
    }
    
    public Block getBlock() {
        return block;
    }
    
    public Vertex getArgsVertex() {
        return argsVertex;
    }

    public boolean getExpandArguments() {
        return expandArguments;
    }

    @Override
    public boolean accept(Propagation propagation, Vertex src) {
        return propagation.getGraph().propagateYieldVertex(propagation, this, src);
    }

    @Override
    public int hashCode() {
        return template.getAttribute().hashCode();
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }

        if (!(other instanceof YieldVertex)) {
            return false;
        }

        YieldVertex o = (YieldVertex) other;
        return template != null
            && o.template != null
            && template.getAttribute().equals(o.template.getAttribute());
    }
}
import java.util.Iterator;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.HashMap;
import java.util.Queue;
import java.util.Collections;
import java.util.Arrays;
import org.jruby.ast.AliasNode;
import org.jruby.ast.AndNode;
import org.jruby.ast.ArgsCatNode;
import org.jruby.ast.ArgsNode;
import org.jruby.ast.ArgsPushNode;
import org.jruby.ast.ArrayNode;
import org.jruby.ast.AttrAssignNode;
import org.jruby.ast.BackRefNode;
import org.jruby.ast.BeginNode;
import org.jruby.ast.BignumNode;
import org.jruby.ast.BlockArgNode;
import org.jruby.ast.BlockNode;
import org.jruby.ast.BlockPassNode;
import org.jruby.ast.BreakNode;
import org.jruby.ast.CallNode;
import org.jruby.ast.CaseNode;
import org.jruby.ast.ClassNode;
import org.jruby.ast.ClassVarAsgnNode;
import org.jruby.ast.ClassVarDeclNode;
import org.jruby.ast.ClassVarNode;
import org.jruby.ast.Colon2Node;
import org.jruby.ast.Colon3Node;
import org.jruby.ast.ConstDeclNode;
import org.jruby.ast.ConstNode;
import org.jruby.ast.DAsgnNode;
import org.jruby.ast.DRegexpNode;
import org.jruby.ast.DStrNode;
import org.jruby.ast.DSymbolNode;
import org.jruby.ast.DVarNode;
import org.jruby.ast.DXStrNode;
import org.jruby.ast.DefinedNode;
import org.jruby.ast.DefnNode;
import org.jruby.ast.DefsNode;
import org.jruby.ast.DotNode;
import org.jruby.ast.EncodingNode;
import org.jruby.ast.EnsureNode;
import org.jruby.ast.EvStrNode;
import org.jruby.ast.FCallNode;
import org.jruby.ast.FalseNode;
import org.jruby.ast.FixnumNode;
import org.jruby.ast.FlipNode;
import org.jruby.ast.FloatNode;
import org.jruby.ast.ForNode;
import org.jruby.ast.GlobalAsgnNode;
import org.jruby.ast.GlobalVarNode;
import org.jruby.ast.HashNode;
import org.jruby.ast.IfNode;
import org.jruby.ast.InstAsgnNode;
import org.jruby.ast.InstVarNode;
import org.jruby.ast.IterNode;
import org.jruby.ast.LocalAsgnNode;
import org.jruby.ast.LocalVarNode;
import org.jruby.ast.Match2Node;
import org.jruby.ast.Match3Node;
import org.jruby.ast.MatchNode;
import org.jruby.ast.ModuleNode;
import org.jruby.ast.MultipleAsgn19Node;
import org.jruby.ast.MultipleAsgnNode;
import org.jruby.ast.NewlineNode;
import org.jruby.ast.NextNode;
import org.jruby.ast.NilNode;
import org.jruby.ast.NotNode;
import org.jruby.ast.NthRefNode;
import org.jruby.ast.OpAsgnAndNode;
import org.jruby.ast.OpAsgnNode;
import org.jruby.ast.OpAsgnOrNode;
import org.jruby.ast.OpElementAsgnNode;
import org.jruby.ast.OrNode;
import org.jruby.ast.PostExeNode;
import org.jruby.ast.PreExeNode;
import org.jruby.ast.RedoNode;
import org.jruby.ast.RegexpNode;
import org.jruby.ast.RescueBodyNode;
import org.jruby.ast.RescueNode;
import org.jruby.ast.RestArgNode;
import org.jruby.ast.RetryNode;
import org.jruby.ast.ReturnNode;
import org.jruby.ast.RootNode;
import org.jruby.ast.SClassNode;
import org.jruby.ast.SValueNode;
import org.jruby.ast.SelfNode;
import org.jruby.ast.SplatNode;
import org.jruby.ast.StrNode;
import org.jruby.ast.SuperNode;
import org.jruby.ast.SymbolNode;
import org.jruby.ast.ToAryNode;
import org.jruby.ast.TrueNode;
import org.jruby.ast.UndefNode;
import org.jruby.ast.UntilNode;
import org.jruby.ast.VAliasNode;
import org.jruby.ast.VCallNode;
import org.jruby.ast.WhenNode;
import org.jruby.ast.WhileNode;
import org.jruby.ast.XStrNode;
import org.jruby.ast.YieldNode;
import org.jruby.ast.ZArrayNode;
import org.jruby.ast.ZSuperNode;
import org.jruby.ast.MethodDefNode;
import org.jruby.ast.NodeType;
import org.jruby.ast.Node;
import org.jruby.ast.ListNode;
import org.jruby.ast.AssignableNode;
import org.jruby.ast.ZeroArgNode;
import org.jruby.ast.types.INameNode;
import org.jruby.ast.visitor.NodeVisitor;
import org.cx4a.rsense.ruby.Ruby;
import org.cx4a.rsense.ruby.Context;
import org.cx4a.rsense.ruby.RubyClass;
import org.cx4a.rsense.ruby.RubyModule;
import org.cx4a.rsense.ruby.MetaClass;
import org.cx4a.rsense.ruby.IRubyObject;
import org.cx4a.rsense.ruby.RubyObject;
import org.cx4a.rsense.ruby.Visibility;
import org.cx4a.rsense.ruby.Block;
import org.cx4a.rsense.ruby.Frame;
import org.cx4a.rsense.ruby.Scope;
import org.cx4a.rsense.ruby.LocalScope;
import org.cx4a.rsense.ruby.DynamicScope;
import org.cx4a.rsense.ruby.DynamicMethod;
import org.cx4a.rsense.util.Logger;
import org.cx4a.rsense.util.NodeDiff;
import org.cx4a.rsense.util.SourceLocation;
import org.cx4a.rsense.typing.runtime.InstanceFactory;
import org.cx4a.rsense.typing.runtime.VertexHolder;
import org.cx4a.rsense.typing.runtime.Array;
import org.cx4a.rsense.typing.runtime.Hash;
import org.cx4a.rsense.typing.runtime.Method;
import org.cx4a.rsense.typing.runtime.DefaultMethod;
import org.cx4a.rsense.typing.runtime.SpecialMethod;
import org.cx4a.rsense.typing.runtime.Proc;
import org.cx4a.rsense.typing.runtime.RuntimeHelper;
import org.cx4a.rsense.typing.runtime.AnnotationHelper;
import org.cx4a.rsense.typing.runtime.TypeVarMap;
import org.cx4a.rsense.typing.runtime.LoopTag;
import org.cx4a.rsense.typing.runtime.ClassTag;
import org.cx4a.rsense.typing.vertex.Vertex;
import org.cx4a.rsense.typing.vertex.PassThroughVertex;
import org.cx4a.rsense.typing.vertex.TypeVarVertex;
import org.cx4a.rsense.typing.vertex.CallVertex;
import org.cx4a.rsense.typing.vertex.MultipleAsgnVertex;
import org.cx4a.rsense.typing.vertex.ToAryVertex;
import org.cx4a.rsense.typing.vertex.SplatVertex;
import org.cx4a.rsense.typing.vertex.SValueVertex;
import org.cx4a.rsense.typing.vertex.YieldVertex;
import org.cx4a.rsense.typing.annotation.TypeAnnotation;
import org.cx4a.rsense.typing.annotation.TypeVariable;
import org.cx4a.rsense.typing.annotation.ClassType;
import org.cx4a.rsense.typing.annotation.MethodType;
package org.cx4a.rsense.typing;





public class Graph implements NodeVisitor {
    public static final Vertex NULL_VERTEX = new Vertex();

    protected static class DummyCall {
        private MethodDefNode node;
        private Method newMethod;
        private Method oldMethod;
        private IRubyObject receiver;

        public DummyCall(MethodDefNode node, Method newMethod, Method oldMethod, IRubyObject receiver) {
            this.node = node;
            this.newMethod = newMethod;
            this.oldMethod = oldMethod;
            this.receiver = receiver;
        }

        public void force(Graph graph) {
            if (!newMethod.isTemplatesShared()) {
                Map<TemplateAttribute, Template> templates = oldMethod != null ? oldMethod.getTemplates() : null;
                if (templates != null && !templates.isEmpty()) {
                    RuntimeHelper.dummyCallForTemplates(graph, node, newMethod, templates);
                } else {
                    RuntimeHelper.dummyCall(graph, node, newMethod, receiver);
                }
            }
        }
    }

    protected Ruby runtime;