package org.cx4a.rsense.typing; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import org.cx4a.rsense.CodeAssist; import org.jrubyparser.ast.*; import org.jrubyparser.NodeVisitor; import org.cx4a.rsense.ruby.Block; import org.cx4a.rsense.ruby.Context; import org.cx4a.rsense.ruby.DynamicMethod; import org.cx4a.rsense.ruby.Frame; import org.cx4a.rsense.ruby.IRubyObject; import org.cx4a.rsense.ruby.LocalScope; import org.cx4a.rsense.ruby.MetaClass; import org.cx4a.rsense.ruby.Ruby; import org.cx4a.rsense.ruby.RubyClass; import org.cx4a.rsense.ruby.RubyModule; import org.cx4a.rsense.ruby.Visibility; import org.cx4a.rsense.typing.annotation.TypeVariable; import org.cx4a.rsense.typing.runtime.AliasMethod; import org.cx4a.rsense.typing.runtime.AnnotationHelper; import org.cx4a.rsense.typing.runtime.Array; import org.cx4a.rsense.typing.runtime.DefaultMethod; import org.cx4a.rsense.typing.runtime.Hash; import org.cx4a.rsense.typing.runtime.LoopTag; import org.cx4a.rsense.typing.runtime.Method; import org.cx4a.rsense.typing.runtime.ObjectAllocator; import org.cx4a.rsense.typing.runtime.Proc; import org.cx4a.rsense.typing.runtime.RuntimeHelper; import org.cx4a.rsense.typing.runtime.SpecialMethod; import org.cx4a.rsense.typing.runtime.TypeVarMap; import org.cx4a.rsense.typing.runtime.VertexHolder; import org.cx4a.rsense.typing.vertex.CallVertex; import org.cx4a.rsense.typing.vertex.MultipleAsgnVertex; import org.cx4a.rsense.typing.vertex.PassThroughVertex; import org.cx4a.rsense.typing.vertex.SValueVertex; import org.cx4a.rsense.typing.vertex.SplatVertex; import org.cx4a.rsense.typing.vertex.ToAryVertex; import org.cx4a.rsense.typing.vertex.TypeVarVertex; import org.cx4a.rsense.typing.vertex.Vertex; import org.cx4a.rsense.typing.vertex.YieldVertex; import org.cx4a.rsense.util.Logger; import org.cx4a.rsense.util.SourceLocation; public class Graph implements NodeVisitor { public interface EventListener { public enum EventType { DEFINE, CLASS, MODULE, METHOD_MISSING } public static class Event { public final EventType type; // TODO divide into classes public final String name; public final Node node; public final Vertex vertex; public Event(EventType type, String name, Node node) { this.type = type; this.name = name; this.node = node; this.vertex = null; } public Event(EventType type, Vertex vertex) { this.type = type; this.name = null; this.node = null; this.vertex = vertex; } } public void update(Event event); } 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()) { Collection templateAttributes = oldMethod != null ? oldMethod.getTemplates().keySet() : null; if (templateAttributes != null && !templateAttributes.isEmpty()) { RuntimeHelper.dummyCallForTemplates(graph, node, newMethod, templateAttributes); } else { RuntimeHelper.dummyCall(graph, node, newMethod, receiver); } } } } protected Ruby runtime; protected Context context; protected Map specialMethods; protected Queue dummyCallQueue = new LinkedList(); protected List eventListeners; public Graph() { this.runtime = new Ruby(); this.runtime.setObjectAllocator(new ObjectAllocator()); this.context = runtime.getContext(); this.specialMethods = new HashMap(); this.eventListeners = new ArrayList(); init(); } public Ruby getRuntime() { return runtime; } public Map getSpecialMethods() { return specialMethods; } public SpecialMethod getSpecialMethod(String name) { return specialMethods.get(name); } public void addSpecialMethod(String name, SpecialMethod method) { specialMethods.put(name, method); } public void addEventListener(EventListener eventListener) { eventListeners.add(eventListener); } public void removeEventListener(EventListener eventListener) { eventListeners.remove(eventListener); } public void notifyDefineEvent(Node node, Method method) { for (EventListener eventListener : eventListeners) eventListener.update(new EventListener.Event(EventListener.EventType.DEFINE, method.toString(), node)); } public void notifyClassEvent(Node node, RubyModule klass) { for (EventListener eventListener : eventListeners) eventListener.update(new EventListener.Event(EventListener.EventType.CLASS, klass.toString(), node)); } public void notifyModuleEvent(Node node, RubyModule module) { for (EventListener eventListener : eventListeners) eventListener.update(new EventListener.Event(EventListener.EventType.MODULE, module.toString(), node)); } public void notifyMethodMissingEvent(CallVertex vertex) { for (EventListener eventListener : eventListeners) eventListener.update(new EventListener.Event(EventListener.EventType.METHOD_MISSING, vertex)); } private void init() { addSpecialMethod("new", new SpecialMethod() { public void call(Ruby runtime, TypeSet receivers, Vertex[] args, Block block, Result result) { TypeSet accumulator = result.getAccumulator(); if (result.getPrevious() == null) { TypeSet resultTypeSet = accumulator = new TypeSet(); TypeSet newReceivers = new TypeSet(); for (IRubyObject receiver : receivers) { if (receiver instanceof RubyClass) { RubyClass klass = (RubyClass) receiver; if (klass.getMetaClass().searchMethod("new") == null) { if (klass == runtime.getProc()) { // Proc.new {} if (block instanceof Proc) resultTypeSet.add((Proc) block); else Logger.debug("Proc.new for no block is not supported yet"); } else resultTypeSet.add(newInstanceOf((RubyClass) receiver)); } else { newReceivers.add(receiver); } } } if (!newReceivers.isEmpty()) { result .setResultTypeSet(resultTypeSet) .setCallNextMethod(true) .setNextMethodChange(true) .setNextMethodName("new") .setNextMethodReceivers(newReceivers) .setNextMethodBlock(block); return; } } result .setResultTypeSet(accumulator) .setCallNextMethod(true) .setNextMethodChange(true) .setNextMethodName("initialize") .setNextMethodReceivers(accumulator) .setNextMethodBlock(block) .setNextMethodNoReturn(true) .setPrivateVisibility(true); } }); addSpecialMethod("include", new SpecialMethod() { public void call(Ruby runtime, TypeSet receivers, Vertex[] args, Block block, Result result) { boolean included = false; if (args != null) { for (Vertex arg : args) { for (IRubyObject receiver : receivers) { // FIXME log message if (!(receiver instanceof RubyModule)) { // FIXME toplevel receiver = receiver.getMetaClass(); } if (receiver instanceof RubyModule) { RubyModule module = (RubyModule) receiver; for (IRubyObject target : arg.getTypeSet()) { if (target instanceof RubyModule) { module.includeModule((RubyModule) target); included = true; } } } } } } if (!included) { result.setCallNextMethod(true); } } }); addSpecialMethod("[]", new SpecialMethod() { public void call(Ruby runtime, TypeSet receivers, Vertex[] args, Block block, Result result) { Collection arg = null; TypeSet ts = new TypeSet(); for (IRubyObject receiver : receivers) { if (receiver instanceof Hash) { if (args != null && args.length > 0) { Hash hash = (Hash) receiver; Object key = Hash.getRealKey(args[0].getNode()); if (!hash.isModified() && key != null) { Vertex v = hash.get(key); if (v != null) { ts.addAll(v.getTypeSet()); } } } } else if (receiver instanceof Array) { if (args != null && args.length > 0) { Array array = (Array) receiver; Integer n = Vertex.getFixnum(args[0]); if (!array.isModified() && n != null) { Vertex v = array.getElement(n); if (v != null) { ts.addAll(v.getTypeSet()); } } } } else if (receiver instanceof Proc) { if (arg == null) { if (args == null) { arg = Arrays.asList((IRubyObject) RuntimeHelper.createArray(Graph.this, new Vertex[0])); } else if (args.length == 1) { arg = args[0].getTypeSet(); } else { arg = Arrays.asList((IRubyObject) RuntimeHelper.createArray(Graph.this, args)); } } Vertex returnVertex = createFreeVertex(); RuntimeHelper.yield(Graph.this, (Proc) receiver, arg, true, returnVertex); ts.addAll(returnVertex.getTypeSet()); } } if (ts.isEmpty()) { result.setCallNextMethod(true); } else { result.setResultTypeSet(ts); } } }); addSpecialMethod("private", new SpecialMethod() { public void call(Ruby runtime, TypeSet receivers, Vertex[] args, Block block, Result result) { RuntimeHelper.setMethodsVisibility(Graph.this, receivers, args, Visibility.PRIVATE); } }); addSpecialMethod("protected", new SpecialMethod() { public void call(Ruby runtime, TypeSet receivers, Vertex[] args, Block block, Result result) { RuntimeHelper.setMethodsVisibility(Graph.this, receivers, args, Visibility.PROTECTED); } }); addSpecialMethod("public", new SpecialMethod() { public void call(Ruby runtime, TypeSet receivers, Vertex[] args, Block block, Result result) { RuntimeHelper.setMethodsVisibility(Graph.this, receivers, args, Visibility.PUBLIC); } }); addSpecialMethod("module_function", new SpecialMethod() { public void call(Ruby runtime, TypeSet receivers, Vertex[] args, Block block, Result result) { RuntimeHelper.setMethodsVisibility(Graph.this, receivers, args, Visibility.MODULE_FUNCTION); } }); addSpecialMethod("attr", new SpecialMethod() { public void call(Ruby runtime, TypeSet receivers, Vertex[] args, Block block, Result result) { if (args != null && args.length > 0) { RuntimeHelper.defineAttrs(Graph.this, receivers, new Vertex[] { args[0] }, true, args.length > 1); } } }); addSpecialMethod("attr_reader", new SpecialMethod() { public void call(Ruby runtime, TypeSet receivers, Vertex[] args, Block block, Result result) { RuntimeHelper.defineAttrs(Graph.this, receivers, args, true, false); } }); addSpecialMethod("attr_writer", new SpecialMethod() { public void call(Ruby runtime, TypeSet receivers, Vertex[] args, Block block, Result result) { RuntimeHelper.defineAttrs(Graph.this, receivers, args, false, true); } }); addSpecialMethod("attr_accessor", new SpecialMethod() { public void call(Ruby runtime, TypeSet receivers, Vertex[] args, Block block, Result result) { RuntimeHelper.defineAttrs(Graph.this, receivers, args, true, true); } }); addSpecialMethod("alias_method", new SpecialMethod() { public void call(Ruby runtime, TypeSet receivers, Vertex[] args, Block block, Result result) { boolean callNextMethod = true; if (args != null && args.length == 2) { for (IRubyObject receiver : receivers) { if (receiver instanceof RubyModule) { callNextMethod = false; String newName = Vertex.getStringOrSymbol(args[0]); String oldName = Vertex.getStringOrSymbol(args[1]); if (newName != null && oldName != null) { RubyModule module = (RubyModule) receiver; DynamicMethod method = module.getMethod(oldName); if (method instanceof Method) module.addMethod(newName, new AliasMethod(newName, (Method) method)); } } } } result.setCallNextMethod(callNextMethod); } }); addSpecialMethod("unpack", new SpecialMethod() { public void call(Ruby runtime, TypeSet receivers, Vertex[] args, Block block, Result result) { if (args != null && args.length > 0) { String template = Vertex.getString(args[0]); if (template != null) { TypeSet ts = new TypeSet(); for (IRubyObject object : receivers) { if (object.isKindOf(runtime.getString())) { List elements = new ArrayList(); for (char c : template.toCharArray()) { RubyClass type = null; switch (c) { case 'a': case 'A': case 'Z': case 'b': case 'B': case 'h': case 'H': case 'm': case 'M': case 'p': case 'P': case 'u': case 'U': type = runtime.getString(); break; case 'c': case 'C': case 's': case 'S': case 'i': case 'I': case 'l': case 'L': case 'q': case 'Q': case 'n': case 'N': case 'v': case 'V': case 'w': case 'x': case 'X': type = runtime.getInteger(); break; case 'f': case 'd': case 'e': case 'E': case 'g': case 'G': type = runtime.getFloat(); break; } if (type != null) { elements.add(createFreeSingleTypeVertex(newInstanceOf(type))); } } ts.add(RuntimeHelper.createArray(Graph.this, elements.toArray(new Vertex[0]))); } } if (ts.isEmpty()) { result.setCallNextMethod(true); } else { result.setResultTypeSet(ts); } } } } }); addSpecialMethod("proc", new SpecialMethod() { public void call(Ruby runtime, TypeSet receivers, Vertex[] args, Block block, Result result) { TypeSet ts = new TypeSet(); for (IRubyObject receiver : receivers) { DynamicMethod method = receiver.getMetaClass().searchMethod("proc"); if (method != null && method.getModule() == runtime.getKernel()) { if (block instanceof Proc) ts.add((Proc) block); else Logger.debug("proc for no block is not supported yet"); } } if (ts.isEmpty()) result.setCallNextMethod(true); else result.setResultTypeSet(ts); } }); addSpecialMethod("lambda", new SpecialMethod() { public void call(Ruby runtime, TypeSet receivers, Vertex[] args, Block block, Result result) { TypeSet ts = new TypeSet(); for (IRubyObject receiver : receivers) { DynamicMethod method = receiver.getMetaClass().searchMethod("lambda"); if (method != null && method.getModule() == runtime.getKernel()) { if (block instanceof Proc) ts.add((Proc) block); else Logger.debug("lambda for no block is not supported yet"); } } if (ts.isEmpty()) result.setCallNextMethod(true); else result.setResultTypeSet(ts); } }); addSpecialMethod("call", new SpecialMethod() { public void call(Ruby runtime, TypeSet receivers, Vertex[] args, Block block, Result result) { TypeSet ts = new TypeSet(); Vertex argVertex = null; for (IRubyObject receiver : receivers) { if (receiver instanceof Proc) { if (argVertex == null) { TypeSet argts; if (args == null) { argts = new TypeSet(); argts.add(RuntimeHelper.createArray(Graph.this, new Vertex[0])); } else if (args.length == 1) { argts = args[0].getTypeSet(); } else { argts = new TypeSet(); argts.add(RuntimeHelper.createArray(Graph.this, args)); } argVertex = createFreeVertex(argts); argVertex.markUnchanged(); } YieldVertex vertex = new YieldVertex(null, RuntimeHelper.getFrameTemplate(runtime.getContext().getCurrentFrame()), (Proc) receiver, argVertex, true); RuntimeHelper.yield(Graph.this, vertex); ts.addAll(vertex.getTypeSet()); } } if (ts.isEmpty()) { result.setCallNextMethod(true); } else { result.setResultTypeSet(ts); } } }); RubyClass objectClass = runtime.getObject(); RubyClass classClass = runtime.getClassClass(); RubyClass procClass = runtime.getProc(); objectClass.addMethod("class", new DefaultMethod(objectClass, "class", null, null, Visibility.PUBLIC, null) { public Vertex call(Graph graph, Template template, IRubyObject receiver, IRubyObject[] args, Vertex[] argVertices, Block block) { return graph.createFreeSingleTypeVertex(receiver.getMetaClass()); } }); classClass.addMethod("superclass", new DefaultMethod(classClass, "superclass", null, null, Visibility.PUBLIC, null) { public Vertex call(Graph graph, Template template, IRubyObject receiver, IRubyObject[] args, Vertex[] argVertices, Block block) { if (receiver instanceof RubyClass) { return graph.createFreeSingleTypeVertex(((RubyClass) receiver).getSuperClass()); } else { return null; } } }); } public void load(Node newAST) { load(newAST, null); } public void load(Node newAST, Node oldAST) { context.pushMain(); // List partialDiff = null; // if (oldAST != null && nodeDiff != null) { // partialDiff = nodeDiff.diff(newAST, oldAST); // if (partialDiff != null) { // Logger.debug("partial load: %s", partialDiff.size()); // for (Node dirty : partialDiff) { // createVertex(dirty); // } // } // } // if (partialDiff == null) { // createVertex(newAST); // } createVertex(newAST); DummyCall entry; while ((entry = dummyCallQueue.poll()) != null) { entry.force(this); } context.popMain(); } public Vertex createVertex(Node node) { if (node != null) { return (Vertex) node.accept(this); } else { return null; } } public Vertex createFreeVertex() { return new Vertex(); } public Vertex createFreeVertex(TypeSet typeSet) { return new Vertex(null, typeSet); } public Vertex createFreeSingleTypeVertex(IRubyObject type) { Vertex vertex = createFreeVertex(); vertex.addType(type); return vertex; } public VertexHolder createVertexHolder(Vertex vertex) { return new VertexHolder(runtime, vertex); } public VertexHolder createFreeVertexHolder() { return new VertexHolder(runtime, createFreeVertex()); } public Vertex createEmptyVertex(Node node) { return new Vertex(node); } public Vertex createSingleTypeVertex(Node node, IRubyObject type) { Vertex vertex = new Vertex(node, 2); vertex.addType(type); return vertex; } public void addEdgeAndPropagate(Vertex src, Vertex dest) { src.addEdge(dest); propagate(src, dest); } public void addEdgeAndUpdate(Vertex src, Vertex dest) { src.addEdge(dest); dest.update(src); } private Propagation propagation; public boolean propagate(Vertex src, Vertex dest) { startPropagation(); boolean result = dest.accept(propagation, src); endPropagation(); return result; } public boolean propagateEdges(Vertex src) { startPropagation(); boolean result = propagateEdges(propagation, src); endPropagation(); return result; } public boolean propagateEdges(Propagation propagation, Vertex src) { // Edges may change during propagation int size = src.getEdges().size(); for (int i = 0; i < size; i++) { Vertex edge = src.getEdges().get(i); if (!propagation.visit(edge, src)) { return false; } } return true; } public boolean propagateEdge(Vertex src, Vertex dest) { startPropagation(); boolean result = propagateEdge(propagation, src, dest); endPropagation(); return result; } public boolean propagateEdge(Propagation propagation, Vertex src, Vertex dest) { src.addEdge(dest); return dest.accept(propagation, src); } private void startPropagation() { if (propagation == null) { propagation = new Propagation(this); } propagation.retain(); } private void endPropagation() { if (propagation.release()) { propagation = null; } } public IRubyObject newInstanceOf(RubyClass klass) { return runtime.newInstance(klass); } public Object visitAliasNode(AliasNode node) { RubyModule module = context.getFrameModule(); DynamicMethod method = module.getMethod(node.getOldNameString()); if (method instanceof Method) module.addMethod(node.getNewNameString(), new AliasMethod(node.getNewNameString(), (Method) method)); return Vertex.EMPTY; } public Object visitAndNode(AndNode node) { Vertex vertex = createEmptyVertex(node); Vertex firstVertex = createVertex(node.getFirst()); Vertex secondVertex = createVertex(node.getSecond()); addEdgeAndUpdate(secondVertex, vertex); return vertex; } public Object visitArgsNode(ArgsNode node) { unsupportedNode(node); return Vertex.EMPTY; } public Object visitArgsCatNode(ArgsCatNode node) { Vertex vertex = createEmptyVertex(node); Vertex first = createVertex(node.getFirst()); SplatVertex second = new SplatVertex(node, createVertex(node.getSecond())); RuntimeHelper.splatValue(this, second); for (IRubyObject a : first.getTypeSet()) { List elements = new ArrayList(); if (a instanceof Array) { Array array = (Array) a; if (array.getElements() != null) { elements.addAll(Arrays.asList(array.getElements())); } } else { elements.add(createFreeSingleTypeVertex(a)); } for (IRubyObject b : second.getTypeSet()) { if (b instanceof Array) { Array array = (Array) b; if (array.getElements() != null) { elements.addAll(Arrays.asList(array.getElements())); } } else { elements.add(createFreeSingleTypeVertex(a)); } } vertex.addType(RuntimeHelper.createArray(this, elements.toArray(new Vertex[0]))); } return vertex; } public Object visitArgsPushNode(ArgsPushNode node) { unsupportedNode(node); return Vertex.EMPTY; } public Object visitArgumentNode(ArgumentNode node) { return createEmptyVertex(node); } public Object visitArrayNode(ArrayNode node) { return RuntimeHelper.createArrayVertex(this, node, RuntimeHelper.toVertices(this, node)); } public Object visitAttrAssignNode(AttrAssignNode node) { Vertex receiverVertex = createVertex(node.getReceiver()); Vertex[] argVertices = null; if (node.getArgs() != null) { List argNodes = node.getArgs().childNodes(); argVertices = new Vertex[argNodes.size()]; for (int i = 0; i < argVertices.length; i++) { argVertices[i] = createVertex(argNodes.get(i)); } } CallVertex vertex = new CallVertex(node, receiverVertex, argVertices, null); return RuntimeHelper.call(this, vertex); } public Object visitBackRefNode(BackRefNode node) { return createSingleTypeVertex(node, newInstanceOf(runtime.getString())); } public Object visitBeginNode(BeginNode node) { return createVertex(node.getBody()); } public Object visitBignumNode(BignumNode node) { return createSingleTypeVertex(node, newInstanceOf(runtime.getBignum())); } public Object visitBlockArgNode(BlockArgNode node) { unsupportedNode(node); return Vertex.EMPTY; } public Object visitBlockArg18Node(BlockArg18Node node) { unsupportedNode(node); return Vertex.EMPTY; } public Object visitBlockNode(BlockNode node) { Object result = null; for (Node child : node.childNodes()) { result = createVertex(child); } return result; } public Object visitBlockPassNode(BlockPassNode node) { // Never reach here unsupportedNode(node); return Vertex.EMPTY; } public Object visitBreakNode(BreakNode node) { Frame frame = context.getCurrentFrame(); LoopTag loopTag = RuntimeHelper.getFrameLoopTag(frame); if (loopTag != null) { Vertex vertex; if (node.getValue() != null) { vertex = createVertex(node.getValue()); } else { vertex = createEmptyVertex(node); } if (loopTag.getYieldVertex() != null) addEdgeAndPropagate(vertex, loopTag.getYieldVertex()); else Logger.debug("no yield vertex"); return vertex; } return Vertex.EMPTY; } public Object visitCommentNode(CommentNode node) { return Vertex.EMPTY; } public Object visitConstDeclNode(ConstDeclNode node) { return RuntimeHelper.constDeclaration(this, node); } public Object visitClassVarAsgnNode(ClassVarAsgnNode node) { return RuntimeHelper.classVarAssign(this, node); } public Object visitClassVarDeclNode(ClassVarDeclNode node) { return RuntimeHelper.classVarDeclaration(this, node); } public Object visitClassVarNode(ClassVarNode node) { return RuntimeHelper.classVariable(this, node); } public Object visitCallNode(CallNode node) { Vertex receiverVertex = createVertex(node.getReceiver()); Vertex[] argVertices = RuntimeHelper.setupCallArgs(this, node.getArgs()); Block block = RuntimeHelper.setupCallBlock(this, node.getIter()); CallVertex vertex = new CallVertex(node, receiverVertex, argVertices, block); return RuntimeHelper.call(this, vertex); } public Object visitCaseNode(CaseNode node) { // FIXME eval === Vertex vertex = createEmptyVertex(node); if (node.getCase() != null) { createVertex(node.getCase()); } ListNode cases = node.getCases(); if (cases != null) { for (int i = 0; i < cases.size(); i++) { WhenNode when = (WhenNode) cases.get(i); if (when.getBody() != null) { Vertex v = createVertex(when.getBody()); addEdgeAndUpdate(v, vertex); } } } if (node.getElse() != null) { Vertex v = createVertex(node.getElse()); addEdgeAndUpdate(v, vertex); } return vertex; } public Object visitClassNode(ClassNode node) { Colon3Node cpath = node.getCPath(); String name = cpath.getName(); RubyModule module = RuntimeHelper.getNamespace(this, cpath); if (module == null) { // Logger.error(SourceLocation.of(node), "namespace unresolved: %s", cpath); return Vertex.EMPTY; } RubyClass superClass = null; if (node.getSuper() != null) { Vertex v = createVertex(node.getSuper()); if (v != null) { IRubyObject superObj = v.singleType(); if (superObj instanceof RubyClass) { superClass = (RubyClass) superObj; } } if (superClass == null) { // Logger.error("superclass not found: %s", cpath.getName()); } } RubyModule klass = module.defineOrGetClassUnder(name, superClass, SourceLocation.of(node)); if (klass != null) { context.pushFrame(klass, name, klass, null, Visibility.PUBLIC); context.pushScope(new LocalScope(klass)); RuntimeHelper.classPartialUpdate(this, klass, node.getBody()); context.popScope(); context.popFrame(); RuntimeHelper.setClassTag(klass, node.getBody(), AnnotationHelper.parseAnnotations(node.getPreviousComments(), node.getPosition().getStartLine())); notifyClassEvent(node, klass); } return Vertex.EMPTY; } public Object visitColon2Node(Colon2Node node) { RubyModule target = RuntimeHelper.getNamespace(this, node); if (target != null) { IRubyObject value = target.getConstant(node.getName()); if (value instanceof VertexHolder) { return ((VertexHolder) value).getVertex(); } else if (value != null) { return createSingleTypeVertex(node, value); } else { return Vertex.EMPTY; } } else { return Vertex.EMPTY; } } public Object visitColon3Node(Colon3Node node) { IRubyObject value = runtime.getObject().getConstant(node.getName()); if (value instanceof VertexHolder) { return ((VertexHolder) value).getVertex(); } else if (value != null) { return createSingleTypeVertex(node, value); } else { return Vertex.EMPTY; } } public Object visitConstNode(ConstNode node) { IRubyObject value = context.getConstant(node.getName()); if (value instanceof VertexHolder) { return ((VertexHolder) value).getVertex(); } else if (value != null) { return createSingleTypeVertex(node, value); } else { return Vertex.EMPTY; } } public Object visitDAsgnNode(DAsgnNode node) { return RuntimeHelper.dynamicAssign(this, node); } public Object visitDRegxNode(DRegexpNode node) { // FIXME eval return createSingleTypeVertex(node, newInstanceOf(runtime.getRegexp())); } public Object visitDStrNode(DStrNode node) { // FIXME eval return createSingleTypeVertex(node, newInstanceOf(runtime.getString())); } public Object visitDSymbolNode(DSymbolNode node) { // FIXME eval return createSingleTypeVertex(node, newInstanceOf(runtime.getSymbol())); } public Object visitDVarNode(DVarNode node) { VertexHolder holder = (VertexHolder) runtime.getContext().getCurrentScope().getValue(node.getName()); return holder != null ? holder.getVertex() : Vertex.EMPTY; } public Object visitDXStrNode(DXStrNode node) { // FIXME eval ` return createSingleTypeVertex(node, newInstanceOf(runtime.getString())); } public Object visitDefinedNode(DefinedNode node) { return createSingleTypeVertex(node, newInstanceOf(runtime.getString())); } public Object visitDefnNode(DefnNode node) { RubyModule cbase = context.getCurrentScope().getModule(); RubyModule klass = context.getFrameModule(); String name = node.getName(); Node bodyNode = node.getBody(); Node argsNode = node.getArgs(); Visibility visibility = context.getFrameVisibility(); boolean moduleFunction = visibility == Visibility.MODULE_FUNCTION; if (name == "initialize" || name == "initialize_copy" || moduleFunction) { visibility = Visibility.PRIVATE; } Method oldMethod = (Method) klass.getMethod(name); Method newMethod = new DefaultMethod(cbase, name, bodyNode, argsNode, visibility, SourceLocation.of(node)); klass.addMethod(name, newMethod); if (moduleFunction) { Method singletonMethod = new DefaultMethod(cbase, name, bodyNode, argsNode, visibility, SourceLocation.of(node)); singletonMethod.setVisibility(Visibility.PUBLIC); klass.getSingletonClass().addMethod(name, singletonMethod); } IRubyObject receiver = newInstanceOf((klass instanceof RubyClass) ? (RubyClass) klass : runtime.getObject()); RuntimeHelper.methodPartialUpdate(this, node, newMethod, oldMethod, receiver); RuntimeHelper.setMethodTag(newMethod, node, AnnotationHelper.parseAnnotations(node.getPreviousComments(), node.getPosition().getStartLine())); dummyCallQueue.offer(new DummyCall(node, newMethod, oldMethod, receiver)); notifyDefineEvent(node, newMethod); return Vertex.EMPTY; } public Object visitDefsNode(DefsNode node) { Vertex receiverVertex = createVertex(node.getReceiver()); if (receiverVertex.isEmpty()) { // Logger.error(SourceLocation.of(node), "null receiver for defs: %s", node.getName()); return Vertex.EMPTY; } RubyModule cbase = context.getCurrentScope().getModule(); String name = node.getName(); for (IRubyObject receiver : receiverVertex.getTypeSet()) { if (receiver instanceof RubyModule) { RubyClass rubyClass = receiver.getSingletonClass(); Node bodyNode = node.getBody(); Node argsNode = node.getArgs(); Method oldMethod = (Method) rubyClass.getMethod(name); Method newMethod = new DefaultMethod(cbase, name, bodyNode, argsNode, Visibility.PUBLIC, SourceLocation.of(node)); rubyClass.addMethod(name, newMethod); RuntimeHelper.methodPartialUpdate(this, node, newMethod, oldMethod, receiver); RuntimeHelper.setMethodTag(newMethod, node, AnnotationHelper.parseAnnotations(node.getPreviousComments(), node.getPosition().getStartLine())); dummyCallQueue.offer(new DummyCall(node, newMethod, oldMethod, receiver)); notifyDefineEvent(node, newMethod); } else { return Vertex.EMPTY; //Logger.warn(SourceLocation.of(node), "cannot define singleton method for individual object: %s", name); } } return Vertex.EMPTY; } public Object visitDotNode(DotNode node) { // FIXME propagation IRubyObject range = newInstanceOf(runtime.getRange()); Vertex beginVertex = createVertex(node.getBegin()); Vertex endVertex = createVertex(node.getEnd()); TypeVarMap typeVarMap = RuntimeHelper.getTypeVarMap(range); if (typeVarMap != null && beginVertex != null && endVertex != null) { Vertex t = createFreeVertex(); t.update(beginVertex); t.update(endVertex); typeVarMap.put(TypeVariable.valueOf("t"), t); } return createSingleTypeVertex(node, range); } public Object visitEncodingNode(EncodingNode node) { unsupportedNode(node); return Vertex.EMPTY; } public Object visitEnsureNode(EnsureNode node) { // FIXME if (node.getEnsure() != null) { createVertex(node.getEnsure()); } return createVertex(node.getBody()); } public Object visitEvStrNode(EvStrNode node) { // never reach here unsupportedNode(node); return Vertex.EMPTY; } public Object visitFCallNode(FCallNode node) { Vertex[] argVertices; Block block; if (node.getArgs() != null && node.getArgs().getNodeType() == NodeType.BLOCKPASSNODE) { BlockPassNode block_pass = (BlockPassNode) node.getArgs(); argVertices = RuntimeHelper.setupCallArgs(this, block_pass.getArgs()); block = RuntimeHelper.setupCallBlock(this, block_pass); } else { argVertices = RuntimeHelper.setupCallArgs(this, node.getArgs()); block = RuntimeHelper.setupCallBlock(this, node.getIter()); } CallVertex vertex = new CallVertex(node, createFreeSingleTypeVertex(context.getFrameSelf()), argVertices, block); vertex.setPrivateVisibility(true); return RuntimeHelper.call(this, vertex); } public Object visitFalseNode(FalseNode node) { return createSingleTypeVertex(node, runtime.getFalse()); } public Object visitFixnumNode(FixnumNode node) { return createSingleTypeVertex(node, newInstanceOf(runtime.getFixnum())); } public Object visitFlipNode(FlipNode node) { // FIXME check booleans (new vertex) return createSingleTypeVertex(node, newInstanceOf(runtime.getBoolean())); } public Object visitFloatNode(FloatNode node) { return createSingleTypeVertex(node, newInstanceOf(runtime.getFloat())); } public Object visitForNode(ForNode node) { Vertex vertex = createEmptyVertex(node); Vertex receiverVertex = createVertex(node.getIter()); Block block = new Proc(runtime, node.getVar(), node.getBody(), context.getCurrentFrame(), context.getCurrentScope()); CallVertex callVertex = new CallVertex(node, "each", receiverVertex, null, block); RuntimeHelper.call(this, callVertex); addEdgeAndUpdate(vertex, callVertex); return vertex; } public Object visitGlobalAsgnNode(GlobalAsgnNode node) { return RuntimeHelper.globalAssign(this, node); } public Object visitGlobalVarNode(GlobalVarNode node) { return RuntimeHelper.globalVariable(this, node); } public Object visitHashNode(HashNode node) { return RuntimeHelper.createHashVertex(this, node, RuntimeHelper.toVertices(this, node.getListNode())); } @Override public Object visitImplicitNilNode(ImplicitNilNode node) { Vertex vertex = createEmptyVertex(node); return vertex; } public Object visitInstAsgnNode(InstAsgnNode node) { return RuntimeHelper.instanceAssign(this, node); } public Object visitInstVarNode(InstVarNode node) { return RuntimeHelper.instanceVariable(this, node); } public Object visitIfNode(IfNode node) { Vertex vertex = createEmptyVertex(node); createVertex(node.getCondition()); Vertex thenBodyVertex = null; if (node.getThenBody() != null) { thenBodyVertex = createVertex(node.getThenBody()); addEdgeAndUpdate(thenBodyVertex, vertex); } Vertex elseBodyVertex = null; if (node.getElseBody() != null) { elseBodyVertex = createVertex(node.getElseBody()); addEdgeAndUpdate(elseBodyVertex, vertex); } return vertex; } public Object visitIterNode(IterNode node) { unsupportedNode(node); return Vertex.EMPTY; } @Override public Object visitKeywordArgNode(KeywordArgNode node) { throw new UnsupportedOperationException("Not supported yet."); } @Override public Object visitKeywordRestArgNode(KeywordRestArgNode node) { throw new UnsupportedOperationException("Not supported yet."); } @Override public Object visitLambdaNode(LambdaNode node) { Vertex vertex = createEmptyVertex(node); return vertex; } public Object visitListNode(ListNode node) { Vertex vertex = createEmptyVertex(node); return vertex; } public Object visitLiteralNode(LiteralNode node) { unsupportedNode(node); return Vertex.EMPTY; } public Object visitLocalAsgnNode(LocalAsgnNode node) { return RuntimeHelper.localAssign(this, node); } public Object visitLocalVarNode(LocalVarNode node) { VertexHolder holder = (VertexHolder) runtime.getContext().getCurrentScope().getValue(node.getName()); return holder != null ? holder.getVertex() : Vertex.EMPTY; } @Override public Object visitMethodNameNode(MethodNameNode node) { // Earlier versions of jruby-parser had this an ArgsNode unsupportedNode(node); return Vertex.EMPTY; } public Object visitMultipleAsgnNode(MultipleAsgnNode node) { return RuntimeHelper.multipleAssign(this, node); } public Object visitMatch2Node(Match2Node node) { // FIXME speedup node return createSingleTypeVertex(node, newInstanceOf(runtime.getMatchData())); } public Object visitMatch3Node(Match3Node node) { // FIXME speedup node return createSingleTypeVertex(node, newInstanceOf(runtime.getMatchData())); } public Object visitMatchNode(MatchNode node) { // FIXME speedup node return createSingleTypeVertex(node, newInstanceOf(runtime.getMatchData())); } public Object visitModuleNode(ModuleNode node) { Colon3Node cpath = node.getCPath(); String name = cpath.getName(); RubyModule enclosingModule = RuntimeHelper.getNamespace(this, cpath); if (enclosingModule == null) { Logger.error(SourceLocation.of(node), "namespace unresolved: %s", name); return Vertex.EMPTY; } RubyModule module = enclosingModule.defineOrGetModuleUnder(name, SourceLocation.of(node)); if (module != null) { context.pushFrame(module, name, module, null, Visibility.PUBLIC); context.pushScope(new LocalScope(module)); RuntimeHelper.classPartialUpdate(this, module, node.getBody()); context.popScope(); context.popFrame(); RuntimeHelper.setClassTag(module, node.getBody(), AnnotationHelper.parseAnnotations(node.getPreviousComments(), node.getPosition().getStartLine())); notifyModuleEvent(node, module); } return Vertex.EMPTY; } public Object visitNewlineNode(NewlineNode node) { return createVertex(node.getNextNode()); } public Object visitNextNode(NextNode node) { Frame frame = context.getCurrentFrame(); LoopTag loopTag = RuntimeHelper.getFrameLoopTag(frame); if (loopTag != null && loopTag.getYieldVertex() != null) { Vertex vertex; if (node.getValue() != null) { vertex = createVertex(node.getValue()); } else { vertex = createEmptyVertex(node); } if (loopTag.getYieldVertex() != null) addEdgeAndPropagate(vertex, loopTag.getYieldVertex()); else Logger.debug("no yield vertex"); return vertex; } return Vertex.EMPTY; } public Object visitNilNode(NilNode node) { return createSingleTypeVertex(node, runtime.getNil()); } public Object visitNotNode(NotNode node) { createVertex(node.getCondition()); return createSingleTypeVertex(node, newInstanceOf(runtime.getBoolean())); } public Object visitNthRefNode(NthRefNode node) { // FIXME return createSingleTypeVertex(node, newInstanceOf(runtime.getString())); } public Object visitOpElementAsgnNode(OpElementAsgnNode node) { String operator = node.getOperatorName(); Vertex receiverVertex = createVertex(node.getReceiver()); Vertex[] argVertices = null; if (node.getArgs() != null) { List argNodes = node.getArgs().childNodes(); argVertices = new Vertex[argNodes.size()]; for (int i = 0; i < argVertices.length; i++) { argVertices[i] = createVertex(argNodes.get(i)); } } Vertex src = createVertex(node.getValue()); Vertex value; if (operator.equals("||") || operator.equals("&&")) { // do nothing value = src; } else { CallVertex getter = new CallVertex(node.getReceiver(), "[]", receiverVertex, argVertices, null); CallVertex op = new CallVertex(node, operator, RuntimeHelper.call(this, getter), new Vertex[] {src}, null); value = RuntimeHelper.call(this, op); } Vertex[] expandedArgs = new Vertex[argVertices.length + 1]; System.arraycopy(argVertices, 0, expandedArgs, 0, argVertices.length); expandedArgs[expandedArgs.length - 1] = value; CallVertex setter = new CallVertex(node, "[]=", receiverVertex, expandedArgs, null); return RuntimeHelper.call(this, setter); } public Object visitOpAsgnNode(OpAsgnNode node) { String operator = node.getOperatorName(); String var = node.getVariableName(); String varAsgn = (node.getVariableName() + "="); Vertex receiverVertex = createVertex(node.getReceiver()); Vertex src = createVertex(node.getValue()); Vertex value; if (operator.equals("||") || operator.equals("&&")) { // do nothing value = src; } else { CallVertex getter = new CallVertex(node.getValue(), var, receiverVertex, null, null); CallVertex op = new CallVertex(node.getValue(), operator, RuntimeHelper.call(this, getter), new Vertex[] {src}, null); value = RuntimeHelper.call(this, op); } CallVertex setter = new CallVertex(node.getValue(), varAsgn, receiverVertex, new Vertex[] {value}, null); return RuntimeHelper.call(this, setter); } public Object visitOpAsgnAndNode(OpAsgnAndNode node) { Vertex vertex = createEmptyVertex(node); Vertex firstVertex = createVertex(node.getFirst()); Vertex secondVertex = createVertex(node.getSecond()); addEdgeAndUpdate(firstVertex, vertex); addEdgeAndUpdate(secondVertex, vertex); return vertex; } public Object visitOpAsgnOrNode(OpAsgnOrNode node) { Vertex vertex = createEmptyVertex(node); Vertex firstVertex = createVertex(node.getFirst()); Vertex secondVertex = createVertex(node.getSecond()); addEdgeAndUpdate(firstVertex, vertex); addEdgeAndUpdate(secondVertex, vertex); return vertex; } public Object visitOptArgNode(OptArgNode node) { Vertex vertex = createEmptyVertex(node); return vertex; } public Object visitOrNode(OrNode node) { Vertex vertex = createEmptyVertex(node); Vertex firstVertex = createVertex(node.getFirst()); Vertex secondVertex = createVertex(node.getSecond()); addEdgeAndUpdate(firstVertex, vertex); addEdgeAndUpdate(secondVertex, vertex); return vertex; } public Object visitPreExeNode(PreExeNode node) { unsupportedNode(node); return Vertex.EMPTY; } public Object visitPostExeNode(PostExeNode node) { unsupportedNode(node); return Vertex.EMPTY; } public Object visitRedoNode(RedoNode node) { return Vertex.EMPTY; } public Object visitRegexpNode(RegexpNode node) { return createSingleTypeVertex(node, newInstanceOf(runtime.getRegexp())); } public Object visitRescueBodyNode(RescueBodyNode node) { if (node.getBody() != null) { return createVertex(node.getBody()); } return Vertex.EMPTY; } public Object visitRescueNode(RescueNode node) { Vertex result = Vertex.EMPTY; if (node.getBody() != null) { result = createVertex(node.getBody()); } if (node.getRescue() != null) { createVertex(node.getRescue()); } if (node.getElse() != null) { result = createVertex(node.getElse()); } return result; } public Object visitRestArgNode(RestArgNode node) { unsupportedNode(node); return Vertex.EMPTY; } public Object visitRetryNode(RetryNode node) { return Vertex.EMPTY; } public Object visitReturnNode(ReturnNode node) { Frame frame = context.getCurrentFrame(); Template template = RuntimeHelper.getFrameTemplate(frame); if (template != null) { Vertex vertex; if (node.getValue() != null) { vertex = createVertex(node.getValue()); } else { vertex = createEmptyVertex(node); } addEdgeAndPropagate(vertex, template.getReturnVertex()); return vertex; } return Vertex.EMPTY; } public Object visitRootNode(RootNode node) { return createVertex(node.getBody()); } public Object visitSClassNode(SClassNode node) { Vertex receiverVertex = createVertex(node.getReceiver()); if (receiverVertex != null) { for (IRubyObject object : receiverVertex.getTypeSet()) { RubyClass klass = object.getMetaClass(); if (klass.isSingleton()) { MetaClass metaClass = (MetaClass) klass; if (metaClass.getAttached() instanceof RubyModule) { context.pushFrame(klass, "sclass", klass, null, Visibility.PUBLIC); context.pushScope(new LocalScope((RubyModule) metaClass.getAttached())); if (node.getBody() != null) { createVertex(node.getBody()); } context.popScope(); context.popFrame(); } return Vertex.EMPTY; } else { // Logger.warn(SourceLocation.of(node), "singleton class of objects is not supported."); } } } return Vertex.EMPTY; } public Object visitSelfNode(SelfNode node) { IRubyObject self = context.getFrameSelf(); if (self == null) { Logger.error(SourceLocation.of(node), "self unresolved"); return Vertex.EMPTY; } else return createSingleTypeVertex(node, self); } public Object visitSplatNode(SplatNode node) { Vertex valueVertex = createVertex(node.getValue()); SplatVertex vertex = new SplatVertex(node, valueVertex); RuntimeHelper.splatValue(this, vertex); return vertex; } public Object visitStrNode(StrNode node) { return createSingleTypeVertex(node, newInstanceOf(runtime.getString())); } public Object visitSyntaxNode(SyntaxNode node) { return Vertex.EMPTY; } public Object visitSuperNode(SuperNode node) { Vertex receiverVertex = createFreeSingleTypeVertex(context.getFrameSelf()); Vertex[] argVertices = null; if (node.getArgs() != null) { List argNodes = node.getArgs().childNodes(); argVertices = new Vertex[argNodes.size()]; for (int i = 0; i < argVertices.length; i++) { argVertices[i] = createVertex(argNodes.get(i)); } } Block block = RuntimeHelper.setupCallBlock(this, node.getIter()); CallVertex vertex = new CallVertex(node, context.getCurrentFrame().getName(), receiverVertex, argVertices, block); vertex.setPrivateVisibility(true); return RuntimeHelper.callSuper(this, vertex); } public Object visitSValueNode(SValueNode node) { SValueVertex vertex = new SValueVertex(node, createVertex(node.getValue())); RuntimeHelper.aValueSplat(this, vertex); return vertex; } public Object visitSymbolNode(SymbolNode node) { return createSingleTypeVertex(node, newInstanceOf(runtime.getSymbol())); } public Object visitToAryNode(ToAryNode node) { Vertex valueVertex = createVertex(node.getValue()); ToAryVertex vertex = new ToAryVertex(node, valueVertex); RuntimeHelper.toAryValue(this, vertex); return vertex; } public Object visitTrueNode(TrueNode node) { return createSingleTypeVertex(node, runtime.getTrue()); } @Override public Object visitUnaryCallNode(UnaryCallNode node) { return createVertex(node.getReceiver()); } public Object visitUndefNode(UndefNode node) { // Logger.warn("undef is not supported yet."); return Vertex.EMPTY; } public Object visitUntilNode(UntilNode node) { Vertex vertex = createEmptyVertex(node); createVertex(node.getCondition()); RuntimeHelper.pushLoopFrame(context, vertex, null); createVertex(node.getBody()); RuntimeHelper.popLoopFrame(context); return vertex; } public Object visitVAliasNode(VAliasNode node) { RuntimeHelper.aliasGlobalVariables(this, node.getNewName(), node.getOldName()); return Vertex.EMPTY; } public Object visitVCallNode(VCallNode node) { CallVertex vertex = new CallVertex(node, createFreeSingleTypeVertex(context.getFrameSelf()), null, null); vertex.setPrivateVisibility(true); return RuntimeHelper.call(this, vertex); } public Object visitWhenNode(WhenNode node) { // never reach here unsupportedNode(node); return Vertex.EMPTY; } public Object visitWhileNode(WhileNode node) { Vertex vertex = createEmptyVertex(node); createVertex(node.getCondition()); RuntimeHelper.pushLoopFrame(context, vertex, null); createVertex(node.getBody()); RuntimeHelper.popLoopFrame(context); return vertex; } public Object visitXStrNode(XStrNode node) { // FIXME eval ` return createSingleTypeVertex(node, newInstanceOf(runtime.getString())); } public Object visitYieldNode(YieldNode node) { Vertex argsVertex = null; if (node.getArgs() != null) { argsVertex = createVertex(node.getArgs()); } YieldVertex vertex = new YieldVertex(node, RuntimeHelper.getFrameTemplate(context.getCurrentFrame()), context.getFrameBlock(), argsVertex, node.getExpandArguments()); return RuntimeHelper.yield(this, vertex); } public Object visitZArrayNode(ZArrayNode node) { return RuntimeHelper.createArrayVertex(this, node, null); } public Object visitZSuperNode(ZSuperNode node) { Template template = RuntimeHelper.getFrameTemplate(context.getCurrentFrame()); if (template != null) { // FIXME more efficient way TemplateAttribute attr = template.getAttribute(); IRubyObject[] args = attr.getArgs(); Vertex receiverVertex = createFreeSingleTypeVertex(context.getFrameSelf()); Vertex[] argVertices = new Vertex[args.length]; for (int i = 0; i < args.length; i++) { argVertices[i] = createFreeSingleTypeVertex(args[i]); } Block block = RuntimeHelper.setupCallBlock(this, node.getIter()); CallVertex vertex = new CallVertex(node, context.getCurrentFrame().getName(), receiverVertex, argVertices, block); vertex.setPrivateVisibility(true); return RuntimeHelper.callSuper(this, vertex); } return Vertex.EMPTY; } public boolean propagateVertex(Propagation propagation, Vertex dest, Vertex src) { // copy first to make sure variable assign can be revisited dest.update(src); if (propagation.checkVisited(dest)) { return true; } return propagateEdges(propagation, dest); } public boolean propagateCallVertex(Propagation propagation, CallVertex dest, Vertex src) { if (propagation.checkVisited(dest)) { return true; } RuntimeHelper.call(this, dest); return propagateEdges(propagation, dest); } public boolean propagateMultipleAsgnVertex(Propagation propagation, MultipleAsgnVertex dest, Vertex src) { if (propagation.checkVisited(dest)) { return true; } if (dest.isChanged()) { dest.markUnchanged(); for (IRubyObject object : RuntimeHelper.arrayValue(propagation.getGraph(), dest.getValueVertex())) { RuntimeHelper.multipleAssign(this, (MultipleAsgnNode) dest.getNode(), object); } } // FIXME no need to propagate? return true; } public boolean propagateSplatVertex(Propagation propagation, SplatVertex dest, Vertex src) { if (propagation.checkVisited(dest)) { return true; } if (dest.isChanged()) { dest.markUnchanged(); RuntimeHelper.splatValue(this, dest); } return propagateEdges(propagation, dest); } public boolean propagateToAryVertex(Propagation propagation, ToAryVertex dest, Vertex src) { if (propagation.checkVisited(dest)) { return true; } if (dest.isChanged()) { dest.markUnchanged(); RuntimeHelper.toAryValue(this, dest); } return propagateEdges(propagation, dest); } public boolean propagateSValueVertex(Propagation propagation, SValueVertex dest, Vertex src) { if (propagation.checkVisited(dest)) { return true; } if (dest.isChanged()) { dest.markUnchanged(); RuntimeHelper.aValueSplat(this, dest); } return propagateEdges(propagation, dest); } public boolean propagateYieldVertex(Propagation propagation, YieldVertex dest, Vertex src) { if (propagation.checkVisited(dest)) { return true; } RuntimeHelper.yield(this, dest); return propagateEdges(propagation, dest); } public boolean propagatePassThroughVertex(Propagation propagation, PassThroughVertex dest, Vertex src) { if (propagation.checkVisited(dest)) { return true; } return propagateEdges(propagation, dest); } public boolean propagateTypeVarVertex(Propagation propagation, TypeVarVertex dest, Vertex src) { if (propagation.checkVisited(dest)) { return true; } dest.getObject().setModified(true); dest.update(src); return propagateEdges(propagation, dest); } private void unsupportedNode(Node node) { Logger.fixme("unsupported node: %s", node); } }