From 1bc527cd92e2d22f60373c62815908ade078e802 Mon Sep 17 00:00:00 2001 From: Sergey Ignatov Date: Wed, 9 Nov 2011 15:07:02 +0400 Subject: [PATCH] supported two cases for constructor calling --- src/org/jetbrains/jet/j2k/Converter.java | 37 ++++++--- src/org/jetbrains/jet/j2k/ast/Block.java | 5 ++ .../jet/j2k/ast/CallChainExpression.java | 4 +- src/org/jetbrains/jet/j2k/ast/Class.java | 30 ++++++- src/org/jetbrains/jet/j2k/ast/ClassType.java | 7 ++ src/org/jetbrains/jet/j2k/ast/Function.java | 12 +++ src/org/jetbrains/jet/j2k/ast/Type.java | 2 +- .../jet/j2k/visitors/ExpressionVisitor.java | 83 +++++++++++++++---- .../jet/j2k/visitors/ThisVisitor.java | 40 +++++++++ .../ast/constructors/file/allCallsPrimary.jav | 20 +++++ .../ast/constructors/file/allCallsPrimary.kt | 23 +++++ .../constructors/file/allCallsPrimary2.jav | 31 +++++++ .../ast/constructors/file/allCallsPrimary2.kt | 33 ++++++++ .../fieldsWithPrimaryPrivateConstructor.kt | 2 +- 14 files changed, 299 insertions(+), 30 deletions(-) create mode 100644 src/org/jetbrains/jet/j2k/visitors/ThisVisitor.java create mode 100644 testData/ast/constructors/file/allCallsPrimary.jav create mode 100644 testData/ast/constructors/file/allCallsPrimary.kt create mode 100644 testData/ast/constructors/file/allCallsPrimary2.jav create mode 100644 testData/ast/constructors/file/allCallsPrimary2.kt diff --git a/src/org/jetbrains/jet/j2k/Converter.java b/src/org/jetbrains/jet/j2k/Converter.java index e0b8c5c88df..7a77ff4be77 100644 --- a/src/org/jetbrains/jet/j2k/Converter.java +++ b/src/org/jetbrains/jet/j2k/Converter.java @@ -7,10 +7,7 @@ import org.jetbrains.jet.j2k.ast.*; import org.jetbrains.jet.j2k.ast.Class; import org.jetbrains.jet.j2k.ast.Enum; import org.jetbrains.jet.j2k.ast.Modifier; -import org.jetbrains.jet.j2k.visitors.ElementVisitor; -import org.jetbrains.jet.j2k.visitors.ExpressionVisitor; -import org.jetbrains.jet.j2k.visitors.StatementVisitor; -import org.jetbrains.jet.j2k.visitors.TypeVisitor; +import org.jetbrains.jet.j2k.visitors.*; import java.util.HashSet; import java.util.LinkedList; @@ -40,7 +37,7 @@ public class Converter { } public static AnonymousClass anonymousClassToAnonymousClass(PsiAnonymousClass anonymousClass) { // TODO: replace by Block, - // use class.getChild() method + // use class.getChild() method return new AnonymousClass( classesToClassList(anonymousClass.getAllInnerClasses()), methodsToFunctionList(anonymousClass.getMethods(), true), @@ -121,6 +118,29 @@ public class Converter { return result; } + @Nullable + public static PsiMethod getPrimaryConstructor(PsiClass psiClass) { + ThisVisitor tv = new ThisVisitor(); + psiClass.accept(tv); + return tv.getPrimaryConstructor(); + } + + public static boolean isConstructorPrimary(@Nullable PsiMethod constructor) { + if (constructor == null) + return false; + if (constructor.getParent() instanceof PsiClass) { + final PsiClass parent = (PsiClass) constructor.getParent(); + if (parent.getConstructors().length == 1) + return true; + else { + PsiMethod c = getPrimaryConstructor(parent); // TODO: move up to classToClass() method + if (c != null && c.hashCode() == constructor.hashCode()) + return true; + } + } + return false; + } + @NotNull private static Function methodToFunction(PsiMethod method, boolean notEmpty) { final IdentifierImpl identifier = new IdentifierImpl(method.getName()); @@ -135,11 +155,8 @@ public class Converter { if (method.getParent() instanceof PsiClass && ((PsiClass) method.getParent()).isInterface()) modifiers.remove(Modifier.ABSTRACT); - - if (method.isConstructor()) { - boolean isPrimary = false; - if (method.getParent() instanceof PsiClass && ((PsiClass) method.getParent()).getConstructors().length == 1) - isPrimary = true; + if (method.isConstructor()) { // TODO: simplify + boolean isPrimary = isConstructorPrimary(method); return new Constructor( identifier, diff --git a/src/org/jetbrains/jet/j2k/ast/Block.java b/src/org/jetbrains/jet/j2k/ast/Block.java index 9166a3586d3..a0f3f789fd4 100644 --- a/src/org/jetbrains/jet/j2k/ast/Block.java +++ b/src/org/jetbrains/jet/j2k/ast/Block.java @@ -11,6 +11,11 @@ import java.util.List; */ public class Block extends Statement { public final static Block EMPTY_BLOCK = new Block(); + + public List getStatements() { + return myStatements; + } + private List myStatements = new LinkedList(); private boolean myNotEmpty = false; diff --git a/src/org/jetbrains/jet/j2k/ast/CallChainExpression.java b/src/org/jetbrains/jet/j2k/ast/CallChainExpression.java index ae57d9a119a..17ab3f222c1 100644 --- a/src/org/jetbrains/jet/j2k/ast/CallChainExpression.java +++ b/src/org/jetbrains/jet/j2k/ast/CallChainExpression.java @@ -7,9 +7,9 @@ import org.jetbrains.annotations.NotNull; */ public class CallChainExpression extends Expression { private Expression myExpression; - private IdentifierImpl myIdentifier; + private Expression myIdentifier; - public CallChainExpression(Expression expression, IdentifierImpl identifier) { + public CallChainExpression(Expression expression, Expression identifier) { myExpression = expression; myIdentifier = identifier; } diff --git a/src/org/jetbrains/jet/j2k/ast/Class.java b/src/org/jetbrains/jet/j2k/ast/Class.java index 4c0ddded791..79bd76c6453 100644 --- a/src/org/jetbrains/jet/j2k/ast/Class.java +++ b/src/org/jetbrains/jet/j2k/ast/Class.java @@ -4,6 +4,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.j2k.util.AstUtil; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -83,6 +84,30 @@ public class Class extends Member { return result; } + @NotNull + List secondaryConstructorsAsStaticInitFunction() { + final LinkedList result = new LinkedList(); + for (Function m : myMethods) + if (m.getKind() == Kind.CONSTRUCTOR && !((Constructor) m).isPrimary()) { + Set modifiers = new HashSet(m.myModifiers); + modifiers.add(Modifier.STATIC); + + final List statements = m.getBlock().getStatements(); + statements.add(new ReturnStatement(new IdentifierImpl("__"))); // TODO: move to one place, find other __ usages + final Block block = new Block(statements); + + result.add(new Function( + new IdentifierImpl("init"), + modifiers, + new ClassType(myName, false), + m.getTypeParameters(), + m.getParams(), + block + )); + } + return result; + } + String typeParametersToKotlin() { return myTypeParameters.size() > 0 ? "<" + AstUtil.joinNodes(myTypeParameters, COMMA_WITH_SPACE) + ">" : EMPTY; } @@ -124,9 +149,9 @@ public class Class extends Member { String bodyToKotlin() { return SPACE + "{" + N + - classObjectToKotlin() + N + AstUtil.joinNodes(getNonStatic(myFields), N) + N + primaryConstructorBodyToKotlin() + N + + classObjectToKotlin() + N + AstUtil.joinNodes(getNonStatic(methodsExceptConstructors()), N) + N + AstUtil.joinNodes(getNonStatic(myInnerClasses), N) + N + "}"; @@ -149,8 +174,9 @@ public class Class extends Member { } private String classObjectToKotlin() { + final List staticMethods = new LinkedList(secondaryConstructorsAsStaticInitFunction()); + staticMethods.addAll(getStatic(methodsExceptConstructors())); final List staticFields = getStatic(myFields); - final List staticMethods = getStatic(methodsExceptConstructors()); final List staticInnerClasses = getStatic(myInnerClasses); if (staticFields.size() + staticMethods.size() + staticInnerClasses.size() > 0) { return "class" + SPACE + "object" + SPACE + "{" + N + diff --git a/src/org/jetbrains/jet/j2k/ast/ClassType.java b/src/org/jetbrains/jet/j2k/ast/ClassType.java index 5ab9205c7f1..2dbb513c316 100644 --- a/src/org/jetbrains/jet/j2k/ast/ClassType.java +++ b/src/org/jetbrains/jet/j2k/ast/ClassType.java @@ -3,6 +3,7 @@ package org.jetbrains.jet.j2k.ast; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.j2k.util.AstUtil; +import java.util.LinkedList; import java.util.List; /** @@ -17,6 +18,12 @@ public class ClassType extends Type { myParameters = parameters; } + public ClassType(Identifier type, boolean isNullable) { + myType = type; + myNullable = isNullable; + myParameters = new LinkedList(); + } + @NotNull @Override public String toKotlin() { diff --git a/src/org/jetbrains/jet/j2k/ast/Function.java b/src/org/jetbrains/jet/j2k/ast/Function.java index 46b44d8ab82..2fd522232d2 100644 --- a/src/org/jetbrains/jet/j2k/ast/Function.java +++ b/src/org/jetbrains/jet/j2k/ast/Function.java @@ -26,6 +26,18 @@ public class Function extends Member { myBlock = block; } + public List getTypeParameters() { + return myTypeParameters; + } + + public Element getParams() { + return myParams; + } + + public Block getBlock() { + return myBlock; + } + private String typeParametersToKotlin() { return myTypeParameters.size() > 0 ? "<" + AstUtil.joinNodes(myTypeParameters, COMMA_WITH_SPACE) + ">" : EMPTY; } diff --git a/src/org/jetbrains/jet/j2k/ast/Type.java b/src/org/jetbrains/jet/j2k/ast/Type.java index e8f27fd9eb1..ad16865772f 100644 --- a/src/org/jetbrains/jet/j2k/ast/Type.java +++ b/src/org/jetbrains/jet/j2k/ast/Type.java @@ -7,7 +7,7 @@ import org.jetbrains.annotations.NotNull; */ public abstract class Type extends Element { public static final Type EMPTY_TYPE = new EmptyType(); - private boolean myNullable = true; + boolean myNullable = true; @NotNull @Override diff --git a/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.java b/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.java index 965e0430a51..4ff5ed04c8d 100644 --- a/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.java +++ b/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.java @@ -4,6 +4,7 @@ import com.intellij.psi.*; import com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl; import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.j2k.Converter; import org.jetbrains.jet.j2k.ast.*; import java.util.List; @@ -189,7 +190,7 @@ public class ExpressionVisitor extends StatementVisitor implements Visitor { typeToType(expression.getType()), // TODO: remove new ExpressionList(callExpression) ); - } else { + } else { // new Class(): common case myResult = new NewClassExpression( elementToElement(expression.getClassOrAnonymousClassReference()), elementToElement(expression.getArgumentList()), @@ -197,6 +198,13 @@ public class ExpressionVisitor extends StatementVisitor implements Visitor { anonymousClassToAnonymousClass(expression.getAnonymousClass()) : null ); + // is constructor secondary + final PsiMethod constructor = expression.resolveMethod(); + if (constructor != null && !isConstructorPrimary(constructor)) { + myResult = new CallChainExpression( + new IdentifierImpl(constructor.getName(), false), + new MethodCallExpression(new IdentifierImpl("init"), elementToElement(expression.getArgumentList()), false)); + } } } @@ -230,44 +238,92 @@ public class ExpressionVisitor extends StatementVisitor implements Visitor { public void visitReferenceExpression(PsiReferenceExpression expression) { super.visitReferenceExpression(expression); - boolean hasDollar = isFinalFieldReference(expression) && isInsideConstructor(expression); - boolean isNullable = typeToType(expression.getType()).isNullable(); + final boolean isFieldReference = isFieldReference(expression); + final boolean hasDollar = isFieldReference && isInsidePrimaryConstructor(expression); + final boolean insideSecondaryConstructor = isInsideSecondaryConstructor(expression); + final boolean hasReceiver = isFieldReference && insideSecondaryConstructor; + final boolean isThis = isThisExpression(expression); + final boolean isNullable = typeToType(expression.getType()).isNullable(); + final String className = getClassName(expression); + + Expression identifier = new IdentifierImpl(expression.getReferenceName(), isNullable); + + if (hasDollar) + identifier = new IdentifierImpl(expression.getReferenceName(), hasDollar, isNullable); + else { + final String temporaryObject = "__"; + if (hasReceiver) + identifier = new CallChainExpression(new IdentifierImpl(temporaryObject, false), new IdentifierImpl(expression.getReferenceName(), isNullable)); + else if (insideSecondaryConstructor && isThis) + identifier = new IdentifierImpl("val " + temporaryObject + " = " + className); // TODO: hack + } - final IdentifierImpl identifier = hasDollar ? - new IdentifierImpl(expression.getReferenceName(), hasDollar, isNullable) : - new IdentifierImpl(expression.getReferenceName(), isNullable); myResult = new CallChainExpression( expressionToExpression(expression.getQualifierExpression()), identifier // TODO: if type exists so identifier is nullable ); } - private boolean isFinalFieldReference(PsiReferenceExpression expression) { + @NotNull + private String getClassName(PsiReferenceExpression expression) { + PsiElement context = expression.getContext(); + while (context != null) { + if (context instanceof PsiMethod && ((PsiMethod) context).isConstructor()) { + final PsiClass containingClass = ((PsiMethod) context).getContainingClass(); + if (containingClass != null) { + final PsiIdentifier identifier = containingClass.getNameIdentifier(); + if (identifier != null) + return identifier.getText(); + } + } + context = context.getContext(); + } + return ""; + } + + private boolean isFieldReference(PsiReferenceExpression expression) { final PsiReference reference = expression.getReference(); if (reference != null) { final PsiElement resolvedReference = reference.resolve(); if (resolvedReference != null) { if (resolvedReference instanceof PsiField) { - final PsiModifierList modifierList = ((PsiField) resolvedReference).getModifierList(); - if (modifierList != null && modifierList.hasExplicitModifier(PsiModifier.FINAL)) { - return true; - } + return true; } } } return false; } - private boolean isInsideConstructor(PsiReferenceExpression expression) { + private boolean isInsideSecondaryConstructor(PsiReferenceExpression expression) { PsiElement context = expression.getContext(); while (context != null) { if (context instanceof PsiMethod && ((PsiMethod) context).isConstructor()) - return true; + return !Converter.isConstructorPrimary((PsiMethod) context); context = context.getContext(); } return false; } + private boolean isInsidePrimaryConstructor(PsiReferenceExpression expression) { + PsiElement context = expression.getContext(); + while (context != null) { + if (context instanceof PsiMethod && ((PsiMethod) context).isConstructor()) + return Converter.isConstructorPrimary((PsiMethod) context); + context = context.getContext(); + } + return false; + } + + private boolean isThisExpression(PsiReferenceExpression expression) { + for (PsiReference r : expression.getReferences()) + if (r.getCanonicalText().equals("this")) { + final PsiElement res = r.resolve(); + if (res != null && res instanceof PsiMethod && ((PsiMethod) res).isConstructor()) + return true; + } + return false; + } + @Override public void visitSuperExpression(PsiSuperExpression expression) { super.visitSuperExpression(expression); @@ -288,7 +344,6 @@ public class ExpressionVisitor extends StatementVisitor implements Visitor { new IdentifierImpl(qualifier.getQualifiedName()) : Identifier.EMPTY_IDENTIFIER ); - } @Override diff --git a/src/org/jetbrains/jet/j2k/visitors/ThisVisitor.java b/src/org/jetbrains/jet/j2k/visitors/ThisVisitor.java new file mode 100644 index 00000000000..96569d20a09 --- /dev/null +++ b/src/org/jetbrains/jet/j2k/visitors/ThisVisitor.java @@ -0,0 +1,40 @@ +package org.jetbrains.jet.j2k.visitors; + +import com.intellij.psi.JavaRecursiveElementVisitor; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiReference; +import com.intellij.psi.PsiReferenceExpression; +import org.jetbrains.annotations.Nullable; + +import java.util.HashSet; + +/** + * @author ignatov + */ +public class ThisVisitor extends JavaRecursiveElementVisitor { + HashSet myResolvedConstructors = new HashSet(); + + public HashSet getResult() { + return myResolvedConstructors; + } + + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + for (PsiReference r : expression.getReferences()) + if (r.getCanonicalText().equals("this")) + if (r.resolve() != null && r.resolve() instanceof PsiMethod && ((PsiMethod) r.resolve()).isConstructor()) + myResolvedConstructors.add((PsiMethod) r.resolve()); + } + + @Nullable + public PsiMethod getPrimaryConstructor() { + if (myResolvedConstructors.size() > 0) { + PsiMethod first = myResolvedConstructors.toArray(new PsiMethod[myResolvedConstructors.size()])[0]; + for (PsiMethod m : myResolvedConstructors) + if (m.hashCode() != first.hashCode()) + return null; + return first; + } + return null; + } +} diff --git a/testData/ast/constructors/file/allCallsPrimary.jav b/testData/ast/constructors/file/allCallsPrimary.jav new file mode 100644 index 00000000000..997b50c969c --- /dev/null +++ b/testData/ast/constructors/file/allCallsPrimary.jav @@ -0,0 +1,20 @@ +class C { + C(int arg1, int arg2, int arg3) { + } + + C(int arg1, int arg2) { + this(arg1, arg2, 0); + } + + C(int arg1) { + this(arg1, 0, 0); + } +} + +public class User { + public static void main() { + C c1 = new C(100, 100, 100); + C c2 = new C(100, 100); + C c3 = new C(100); + } +} \ No newline at end of file diff --git a/testData/ast/constructors/file/allCallsPrimary.kt b/testData/ast/constructors/file/allCallsPrimary.kt new file mode 100644 index 00000000000..c0365493e51 --- /dev/null +++ b/testData/ast/constructors/file/allCallsPrimary.kt @@ -0,0 +1,23 @@ +open class C(arg1 : Int, arg2 : Int, arg3 : Int) { +{ +} +class object { +open fun init(arg1 : Int, arg2 : Int) : C { +val __ = C(arg1, arg2, 0) +return __ +} +open fun init(arg1 : Int) : C { +val __ = C(arg1, 0, 0) +return __ +} +} +} +public open class User() { +class object { +open public fun main() : Unit { +var c1 : C? = C(100, 100, 100) +var c2 : C? = C.init(100, 100) +var c3 : C? = C.init(100) +} +} +} \ No newline at end of file diff --git a/testData/ast/constructors/file/allCallsPrimary2.jav b/testData/ast/constructors/file/allCallsPrimary2.jav new file mode 100644 index 00000000000..45b045051bc --- /dev/null +++ b/testData/ast/constructors/file/allCallsPrimary2.jav @@ -0,0 +1,31 @@ +class C { + final int myArg1; + int myArg2; + int myArg3; + + C(int arg1, int arg2, int arg3) { + this(arg1); + myArg2 = arg2; + myArg3 = arg3; + } + + C(int arg1, int arg2) { + this(arg1); + myArg2 = arg2; + myArg3 = 0; + } + + C(int arg1) { + myArg1 = arg1; + myArg2 = 0; + myArg3 = 0; + } +} + +public class User { + public static void main() { + C c1 = new C(100, 100, 100); + C c2 = new C(100, 100); + C c3 = new C(100); + } +} \ No newline at end of file diff --git a/testData/ast/constructors/file/allCallsPrimary2.kt b/testData/ast/constructors/file/allCallsPrimary2.kt new file mode 100644 index 00000000000..787f2f90451 --- /dev/null +++ b/testData/ast/constructors/file/allCallsPrimary2.kt @@ -0,0 +1,33 @@ +open class C(arg1 : Int) { +val myArg1 : Int +var myArg2 : Int +var myArg3 : Int +{ +$myArg1 = arg1 +$myArg2 = 0 +$myArg3 = 0 +} +class object { +open fun init(arg1 : Int, arg2 : Int, arg3 : Int) : C { +val __ = C(arg1) +__.myArg2 = arg2 +__.myArg3 = arg3 +return __ +} +open fun init(arg1 : Int, arg2 : Int) : C { +val __ = C(arg1) +__.myArg2 = arg2 +__.myArg3 = 0 +return __ +} +} +} +public open class User() { +class object { +open public fun main() : Unit { +var c1 : C? = C.init(100, 100, 100) +var c2 : C? = C.init(100, 100) +var c3 : C? = C(100) +} +} +} \ No newline at end of file diff --git a/testData/ast/enum/class/fieldsWithPrimaryPrivateConstructor.kt b/testData/ast/enum/class/fieldsWithPrimaryPrivateConstructor.kt index 52e96f54c6d..6a04cba471b 100644 --- a/testData/ast/enum/class/fieldsWithPrimaryPrivateConstructor.kt +++ b/testData/ast/enum/class/fieldsWithPrimaryPrivateConstructor.kt @@ -6,7 +6,7 @@ YELLOW(24) BLUE(25) private var code : Int open private (c : Int) { -code = c +$code = c } open public fun getCode() : Int { return code