From cb7c05e5509ef9532a8e50339519964c9e0a0acc Mon Sep 17 00:00:00 2001 From: Pavel Talanov Date: Wed, 23 Nov 2011 16:31:24 +0400 Subject: [PATCH] Fixed design flaw which prevented base class from being inherited if it was defined after the derived class. --- .../k2js/translate/BindingUtils.java | 13 ++ .../translate/ClassDeclarationTranslator.java | 22 +- .../org/jetbrains/k2js/utils/ClassSorter.java | 83 +++++++ .../org/jetbrains/k2js/utils/DiGraphNode.java | 170 ++++++++++++++ .../k2js/utils/PartiallyOrderedSet.java | 209 ++++++++++++++++++ .../k2js/test/ClassInheritanceTest.java | 5 + .../cases/baseClassDefinedAfterDerived.kt | 13 ++ 7 files changed, 509 insertions(+), 6 deletions(-) create mode 100644 translator/src/org/jetbrains/k2js/utils/ClassSorter.java create mode 100644 translator/src/org/jetbrains/k2js/utils/DiGraphNode.java create mode 100644 translator/src/org/jetbrains/k2js/utils/PartiallyOrderedSet.java create mode 100644 translator/testFiles/inheritance/cases/baseClassDefinedAfterDerived.kt diff --git a/translator/src/org/jetbrains/k2js/translate/BindingUtils.java b/translator/src/org/jetbrains/k2js/translate/BindingUtils.java index c54c0bc5341..c23af55bffb 100644 --- a/translator/src/org/jetbrains/k2js/translate/BindingUtils.java +++ b/translator/src/org/jetbrains/k2js/translate/BindingUtils.java @@ -78,10 +78,23 @@ public final class BindingUtils { return result; } + @NotNull + static public JetClass getClassForDescriptor(@NotNull BindingContext context, + @NotNull ClassDescriptor descriptor) { + PsiElement result = context.get(BindingContext.DESCRIPTOR_TO_DECLARATION, descriptor); + assert result instanceof JetClass : "ClassDescriptor should have declaration of type JetClass"; + return (JetClass) result; + } + @NotNull static public List getSuperclassDescriptors(@NotNull BindingContext context, @NotNull JetClass classDeclaration) { ClassDescriptor classDescriptor = getClassDescriptor(context, classDeclaration); + return getSuperclassDescriptors(classDescriptor); + } + + @NotNull + static public List getSuperclassDescriptors(@NotNull ClassDescriptor classDescriptor) { Collection superclassTypes = classDescriptor.getTypeConstructor().getSupertypes(); List superClassDescriptors = new ArrayList(); for (JetType type : superclassTypes) { diff --git a/translator/src/org/jetbrains/k2js/translate/ClassDeclarationTranslator.java b/translator/src/org/jetbrains/k2js/translate/ClassDeclarationTranslator.java index 40e72965ab4..948669f08ba 100644 --- a/translator/src/org/jetbrains/k2js/translate/ClassDeclarationTranslator.java +++ b/translator/src/org/jetbrains/k2js/translate/ClassDeclarationTranslator.java @@ -7,6 +7,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.psi.JetClass; import org.jetbrains.jet.lang.psi.JetDeclaration; import org.jetbrains.jet.lang.psi.JetNamespace; +import org.jetbrains.k2js.utils.ClassSorter; import java.util.ArrayList; import java.util.HashMap; @@ -59,7 +60,7 @@ public final class ClassDeclarationTranslator extends AbstractTranslator { @NotNull private JsInvocation generateDummyFunctionInvocation() { JsFunction dummyFunction = JsFunction.getAnonymousFunctionWithScope(dummyFunctionScope); - List classDeclarations = getClassDeclarationStatements(); + List classDeclarations = generateClassDeclarationStatements(); classDeclarations.add(new JsReturn(generateReturnedObjectLiteral())); dummyFunction.setBody(AstUtil.newBlock(classDeclarations)); return AstUtil.newInvocation(dummyFunction); @@ -80,16 +81,25 @@ public final class ClassDeclarationTranslator extends AbstractTranslator { } @NotNull - private List getClassDeclarationStatements() { + private List generateClassDeclarationStatements() { List classDeclarations = new ArrayList(); - for (JetDeclaration declaration : namespace.getDeclarations()) { - if (declaration instanceof JetClass) { - classDeclarations.add(generateDeclaration((JetClass) declaration)); - } + for (JetClass jetClass : getClassDeclarations()) { + classDeclarations.add(generateDeclaration(jetClass)); } return classDeclarations; } + @NotNull + private List getClassDeclarations() { + List classes = new ArrayList(); + for (JetDeclaration declaration : namespace.getDeclarations()) { + if (declaration instanceof JetClass) { + classes.add((JetClass) declaration); + } + } + return ClassSorter.sortUsingInheritanceOrder(classes, translationContext().bindingContext()); + } + @NotNull private JsStatement generateDeclaration(@NotNull JetClass declaration) { JsName globalClassName = translationContext().getNameForElement(declaration); diff --git a/translator/src/org/jetbrains/k2js/utils/ClassSorter.java b/translator/src/org/jetbrains/k2js/utils/ClassSorter.java new file mode 100644 index 00000000000..cd43068fe3a --- /dev/null +++ b/translator/src/org/jetbrains/k2js/utils/ClassSorter.java @@ -0,0 +1,83 @@ +package org.jetbrains.k2js.utils; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.descriptors.ClassDescriptor; +import org.jetbrains.jet.lang.psi.JetClass; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.k2js.translate.BindingUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Talanov Pavel + */ +public final class ClassSorter { + + @NotNull + private final List classesToSort; + @NotNull + private final List descriptorList; + @NotNull + private final BindingContext bindingContext; + @NotNull + private final PartiallyOrderedSet partiallyOrderedSet = new PartiallyOrderedSet(); + + @NotNull + static public List sortUsingInheritanceOrder(@NotNull List original, + @NotNull BindingContext bindingContext) { + ClassSorter sorter = new ClassSorter(original, bindingContext); + return sorter.sortUsingInheritanceOrder(); + } + + private ClassSorter(@NotNull List original, @NotNull BindingContext bindingContext) { + this.classesToSort = original; + this.bindingContext = bindingContext; + this.descriptorList = getDescriptorList(); + } + + @NotNull + private List sortUsingInheritanceOrder() { + putDescriptorsInPartiallyOrderedSet(); + setInheritanceOrder(); + return getSortedClasses(); + } + + @NotNull + private List getSortedClasses() { + List sortedClasses = new ArrayList(); + for (Object object : partiallyOrderedSet) { + assert object instanceof ClassDescriptor; + sortedClasses.add(BindingUtils.getClassForDescriptor(bindingContext, (ClassDescriptor) object)); + } + return sortedClasses; + } + + private void putDescriptorsInPartiallyOrderedSet() { + partiallyOrderedSet.addAll(descriptorList); + } + + private void setInheritanceOrder() { + for (ClassDescriptor descriptor : getDescriptorList()) { + traverseAncestorsAndSetOrder(descriptor); + } + } + + @NotNull + private List getDescriptorList() { + List descriptorList = new ArrayList(); + for (JetClass jetClass : classesToSort) { + descriptorList.add(BindingUtils.getClassDescriptor(bindingContext, jetClass)); + } + return descriptorList; + } + + private void traverseAncestorsAndSetOrder(@NotNull ClassDescriptor descriptor) { + List superclasses = BindingUtils.getSuperclassDescriptors(descriptor); + for (ClassDescriptor superclass : superclasses) { + partiallyOrderedSet.setOrdering(superclass, descriptor); + traverseAncestorsAndSetOrder(superclass); + } + } + +} diff --git a/translator/src/org/jetbrains/k2js/utils/DiGraphNode.java b/translator/src/org/jetbrains/k2js/utils/DiGraphNode.java new file mode 100644 index 00000000000..fdee94213b7 --- /dev/null +++ b/translator/src/org/jetbrains/k2js/utils/DiGraphNode.java @@ -0,0 +1,170 @@ +package org.jetbrains.k2js.utils; + +/* + * Copyright 2000 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * A node in a directed graph. In addition to an arbitrary + * Object containing user data associated with the node, + * each node maintains a Sets of nodes which are pointed + * to by the current node (available from getOutNodes). + * The in-degree of the node (that is, number of nodes that point to + * the current node) may be queried. + */ +class DiGraphNode implements Cloneable, Serializable { + + /** + * The data associated with this node. + */ + protected Object data; + + /** + * A Set of neighboring nodes pointed to by this + * node. + */ + protected Set outNodes = new HashSet(); + + /** + * The in-degree of the node. + */ + protected int inDegree = 0; + + /** + * A Set of neighboring nodes that point to this + * node. + */ + private Set inNodes = new HashSet(); + + public DiGraphNode(Object data) { + this.data = data; + } + + /** + * Returns the Object referenced by this node. + */ + public Object getData() { + return data; + } + + /** + * Returns an Iterator containing the nodes pointed + * to by this node. + */ + public Iterator getOutNodes() { + return outNodes.iterator(); + } + + /** + * Adds a directed edge to the graph. The outNodes list of this + * node is updated and the in-degree of the other node is incremented. + * + * @param node a DiGraphNode. + * @return true if the node was not previously the + * target of an edge. + */ + public boolean addEdge(DiGraphNode node) { + if (outNodes.contains(node)) { + return false; + } + + outNodes.add(node); + node.inNodes.add(this); + node.incrementInDegree(); + return true; + } + + /** + * Returns true if an edge exists between this node + * and the given node. + * + * @param node a DiGraphNode. + * @return true if the node is the target of an edge. + */ + public boolean hasEdge(DiGraphNode node) { + return outNodes.contains(node); + } + + /** + * Removes a directed edge from the graph. The outNodes list of this + * node is updated and the in-degree of the other node is decremented. + * + * @return true if the node was previously the target + * of an edge. + */ + public boolean removeEdge(DiGraphNode node) { + if (!outNodes.contains(node)) { + return false; + } + + outNodes.remove(node); + node.inNodes.remove(this); + node.decrementInDegree(); + return true; + } + + /** + * Removes this node from the graph, updating neighboring nodes + * appropriately. + */ + public void dispose() { + Object[] inNodesArray = inNodes.toArray(); + for (int i = 0; i < inNodesArray.length; i++) { + DiGraphNode node = (DiGraphNode) inNodesArray[i]; + node.removeEdge(this); + } + + Object[] outNodesArray = outNodes.toArray(); + for (int i = 0; i < outNodesArray.length; i++) { + DiGraphNode node = (DiGraphNode) outNodesArray[i]; + removeEdge(node); + } + } + + /** + * Returns the in-degree of this node. + */ + public int getInDegree() { + return inDegree; + } + + /** + * Increments the in-degree of this node. + */ + private void incrementInDegree() { + ++inDegree; + } + + /** + * Decrements the in-degree of this node. + */ + private void decrementInDegree() { + --inDegree; + } +} \ No newline at end of file diff --git a/translator/src/org/jetbrains/k2js/utils/PartiallyOrderedSet.java b/translator/src/org/jetbrains/k2js/utils/PartiallyOrderedSet.java new file mode 100644 index 00000000000..d1bc45a0c9d --- /dev/null +++ b/translator/src/org/jetbrains/k2js/utils/PartiallyOrderedSet.java @@ -0,0 +1,209 @@ +package org.jetbrains.k2js.utils; + +/* + * Copyright 2000 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +import java.util.*; + +/** + * A set of Objects with pairwise orderings between them. + * The iterator method provides the elements in + * topologically sorted order. Elements participating in a cycle + * are not returned. + *

+ * Unlike the SortedSet and SortedMap + * interfaces, which require their elements to implement the + * Comparable interface, this class receives ordering + * information via its setOrdering and + * unsetPreference methods. This difference is due to + * the fact that the relevant ordering between elements is unlikely to + * be inherent in the elements themselves; rather, it is set + * dynamically accoring to application policy. For example, in a + * service provider registry situation, an application might allow the + * user to set a preference order for service provider objects + * supplied by a trusted vendor over those supplied by another. + */ +class PartiallyOrderedSet extends AbstractSet { + + // The topological sort (roughly) follows the algorithm described in + // Horowitz and Sahni, _Fundamentals of Data Structures_ (1976), + // p. 315. + + // Maps Objects to DigraphNodes that contain them + private Map poNodes = new HashMap(); + + // The set of Objects + private Set nodes = poNodes.keySet(); + + /** + * Constructs a PartiallyOrderedSet. + */ + public PartiallyOrderedSet() { + } + + public int size() { + return nodes.size(); + } + + public boolean contains(Object o) { + return nodes.contains(o); + } + + /** + * Returns an iterator over the elements contained in this + * collection, with an ordering that respects the orderings set + * by the setOrdering method. + */ + public Iterator iterator() { + return new PartialOrderIterator(poNodes.values().iterator()); + } + + /** + * Adds an Object to this + * PartiallyOrderedSet. + */ + public boolean add(Object o) { + if (nodes.contains(o)) { + return false; + } + + DiGraphNode node = new DiGraphNode(o); + poNodes.put(o, node); + return true; + } + + /** + * Removes an Object from this + * PartiallyOrderedSet. + */ + public boolean remove(Object o) { + DiGraphNode node = (DiGraphNode) poNodes.get(o); + if (node == null) { + return false; + } + + poNodes.remove(o); + node.dispose(); + return true; + } + + public void clear() { + poNodes.clear(); + } + + /** + * Sets an ordering between two nodes. When an iterator is + * requested, the first node will appear earlier in the + * sequence than the second node. If a prior ordering existed + * between the nodes in the opposite order, it is removed. + * + * @return true if no prior ordering existed + * between the nodes, falseotherwise. + */ + public boolean setOrdering(Object first, Object second) { + DiGraphNode firstPONode = + (DiGraphNode) poNodes.get(first); + DiGraphNode secondPONode = + (DiGraphNode) poNodes.get(second); + + secondPONode.removeEdge(firstPONode); + return firstPONode.addEdge(secondPONode); + } + + /** + * Removes any ordering between two nodes. + * + * @return true if a prior prefence existed between the nodes. + */ + public boolean unsetOrdering(Object first, Object second) { + DiGraphNode firstPONode = + (DiGraphNode) poNodes.get(first); + DiGraphNode secondPONode = + (DiGraphNode) poNodes.get(second); + + return firstPONode.removeEdge(secondPONode) || + secondPONode.removeEdge(firstPONode); + } + + /** + * Returns true if an ordering exists between two + * nodes. + */ + public boolean hasOrdering(Object preferred, Object other) { + DiGraphNode preferredPONode = + (DiGraphNode) poNodes.get(preferred); + DiGraphNode otherPONode = + (DiGraphNode) poNodes.get(other); + + return preferredPONode.hasEdge(otherPONode); + } +} + +class PartialOrderIterator implements Iterator { + + LinkedList zeroList = new LinkedList(); + Map inDegrees = new HashMap(); // DiGraphNode -> Integer + + public PartialOrderIterator(Iterator iter) { + // Initialize scratch in-degree values, zero list + while (iter.hasNext()) { + DiGraphNode node = (DiGraphNode) iter.next(); + int inDegree = node.getInDegree(); + inDegrees.put(node, new Integer(inDegree)); + + // Add nodes with zero in-degree to the zero list + if (inDegree == 0) { + zeroList.add(node); + } + } + } + + public boolean hasNext() { + return !zeroList.isEmpty(); + } + + public Object next() { + DiGraphNode first = (DiGraphNode) zeroList.removeFirst(); + + // For each out node of the output node, decrement its in-degree + Iterator outNodes = first.getOutNodes(); + while (outNodes.hasNext()) { + DiGraphNode node = (DiGraphNode) outNodes.next(); + int inDegree = ((Integer) inDegrees.get(node)).intValue() - 1; + inDegrees.put(node, new Integer(inDegree)); + + // If the in-degree has fallen to 0, place the node on the list + if (inDegree == 0) { + zeroList.add(node); + } + } + + return first.getData(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } +} \ No newline at end of file diff --git a/translator/test/org/jetbrains/k2js/test/ClassInheritanceTest.java b/translator/test/org/jetbrains/k2js/test/ClassInheritanceTest.java index 247cf7e1b39..128ae092eaf 100644 --- a/translator/test/org/jetbrains/k2js/test/ClassInheritanceTest.java +++ b/translator/test/org/jetbrains/k2js/test/ClassInheritanceTest.java @@ -38,6 +38,11 @@ public final class ClassInheritanceTest extends IncludeLibraryTest { public void valuePassedToAncestorConstructor() throws Exception { testFooBoxIsTrue("valuePassedToAncestorConstructor.kt"); } + + @Test + public void baseClassDefinedAfterDerived() throws Exception { + testFooBoxIsTrue("baseClassDefinedAfterDerived.kt"); + } } diff --git a/translator/testFiles/inheritance/cases/baseClassDefinedAfterDerived.kt b/translator/testFiles/inheritance/cases/baseClassDefinedAfterDerived.kt new file mode 100644 index 00000000000..9ef882ca38e --- /dev/null +++ b/translator/testFiles/inheritance/cases/baseClassDefinedAfterDerived.kt @@ -0,0 +1,13 @@ +namespace foo + + +class A() : B() { + +} + +open class B() { + + val a = 3 +} + +fun box() = (A().a == 3) \ No newline at end of file