Java to Kotlin converter: converting usages of Kotlin global and extension functions and properties

This commit is contained in:
Valentin Kipyatkov
2014-06-05 20:18:57 +04:00
parent f343b89501
commit b4ab3983f8
18 changed files with 258 additions and 65 deletions
+1
View File
@@ -12,6 +12,7 @@
<orderEntry type="module" module-name="descriptor.loader.java" /> <orderEntry type="module" module-name="descriptor.loader.java" />
<orderEntry type="library" name="idea-full" level="project" /> <orderEntry type="library" name="idea-full" level="project" />
<orderEntry type="module" module-name="frontend.java" /> <orderEntry type="module" module-name="frontend.java" />
<orderEntry type="module" module-name="jet.as.java.psi" />
</component> </component>
</module> </module>
+1 -15
View File
@@ -58,7 +58,7 @@ public class Converter(val project: Project, val settings: ConverterSettings) {
is PsiExpression -> convertExpression(element) is PsiExpression -> convertExpression(element)
is PsiComment -> Comment(element.getText()!!) is PsiComment -> Comment(element.getText()!!)
is PsiImportList -> convertImportList(element) is PsiImportList -> convertImportList(element)
is PsiImportStatementBase -> convertImport(element) is PsiImportStatementBase -> convertImport(element, false)
is PsiPackageStatement -> PackageStatement(quoteKeywords(element.getPackageName() ?: "")) is PsiPackageStatement -> PackageStatement(quoteKeywords(element.getPackageName() ?: ""))
is PsiWhiteSpace -> WhiteSpace(element.getText()!!) is PsiWhiteSpace -> WhiteSpace(element.getText()!!)
else -> null else -> null
@@ -615,20 +615,6 @@ public class Converter(val project: Project, val settings: ConverterSettings) {
return Parameter(Identifier(parameter.getName()!!), `type`, varValModifier, modifiers) return Parameter(Identifier(parameter.getName()!!), `type`, varValModifier, modifiers)
} }
public fun convertArguments(expression: PsiCallExpression): List<Expression> {
val arguments = expression.getArgumentList()?.getExpressions() ?: array()
val resolved = expression.resolveMethod()
val expectedTypes = if (resolved != null)
resolved.getParameterList().getParameters().map { it.getType() }
else
listOf()
return if (arguments.size == expectedTypes.size())
(0..expectedTypes.lastIndex).map { i -> convertExpression(arguments[i], expectedTypes[i]) }
else
arguments.map { convertExpression(it) }
}
public fun convertExpression(argument: PsiExpression?, expectedType: PsiType?): Expression { public fun convertExpression(argument: PsiExpression?, expectedType: PsiType?): Expression {
if (argument == null) return Identifier.Empty if (argument == null) return Identifier.Empty
+37 -27
View File
@@ -21,41 +21,51 @@ import org.jetbrains.jet.j2k.*
import com.intellij.psi.PsiImportList import com.intellij.psi.PsiImportList
import org.jetbrains.jet.lang.resolve.name.FqName import org.jetbrains.jet.lang.resolve.name.FqName
import org.jetbrains.jet.lang.resolve.java.mapping.JavaToKotlinClassMap import org.jetbrains.jet.lang.resolve.java.mapping.JavaToKotlinClassMap
import org.jetbrains.jet.lang.resolve.name.isValidJavaFqName import com.intellij.psi.PsiJavaCodeReferenceElement
import org.jetbrains.jet.asJava.KotlinLightClassForPackage
class Import(val name: String) : Element { class Import(val name: String) : Element {
override fun toKotlin() = "import " + name override fun toKotlin() = "import " + name
} }
class ImportList(val imports: List<Import>) : Element { class ImportList(private val imports: List<Import>) : Element {
val filteredImports = imports.filter {
!it.name.isEmpty() && it.name !in NOT_NULL_ANNOTATIONS && it.name !in NULLABLE_ANNOTATIONS
}.filter {
// If name is invalid, like with star imports, don't try to filter
if (!isValidJavaFqName(it.name))
true
else {
// If imported class has a kotlin analog, drop the import
val kotlinAnalogsForClass = JavaToKotlinClassMap.getInstance().mapPlatformClass(FqName(it.name))
kotlinAnalogsForClass.isEmpty()
}
}
override val isEmpty: Boolean override val isEmpty: Boolean
get() = filteredImports.isEmpty() get() = imports.isEmpty()
override fun toKotlin() = filteredImports.toKotlin("\n") override fun toKotlin() = imports.toKotlin("\n")
} }
fun Converter.convertImportList(importList: PsiImportList): ImportList = public fun Converter.convertImportList(importList: PsiImportList): ImportList =
ImportList(importList.getAllImportStatements() map { convertImport(it) }) ImportList(importList.getAllImportStatements().map { convertImport(it, true) }.filterNotNull())
fun Converter.convertImport(i: PsiImportStatementBase): Import { public fun Converter.convertImport(anImport: PsiImportStatementBase, filter: Boolean): Import? {
val reference = i.getImportReference() val reference = anImport.getImportReference()
if (reference != null) { if (reference == null) return null
return Import(quoteKeywords(reference.getQualifiedName()!!) + if (i.isOnDemand()) ".*" else "") val qualifiedName = quoteKeywords(reference.getQualifiedName()!!)
if (anImport.isOnDemand()) {
return Import(qualifiedName + ".*")
} }
return Import("") else {
} return if (filter) {
val filteredName = filterImport(qualifiedName, reference)
if (filteredName != null) Import(filteredName) else null
}
else {
Import(qualifiedName)
}
}
}
private fun filterImport(name: String, ref: PsiJavaCodeReferenceElement): String? {
if (name in NOT_NULL_ANNOTATIONS || name in NULLABLE_ANNOTATIONS) return null
// If imported class has a kotlin analog, drop the import
if (!JavaToKotlinClassMap.getInstance().mapPlatformClass(FqName(name)).isEmpty()) return null
val target = ref.resolve()
if (target != null && target is KotlinLightClassForPackage) {
return quoteKeywords(target.getFqName().parent().toString()) + ".*"
}
return name
}
@@ -27,6 +27,11 @@ import org.jetbrains.jet.lang.types.lang.PrimitiveType
import org.jetbrains.jet.j2k.* import org.jetbrains.jet.j2k.*
import org.jetbrains.jet.lang.resolve.java.jetAsJava.KotlinLightMethod import org.jetbrains.jet.lang.resolve.java.jetAsJava.KotlinLightMethod
import org.jetbrains.jet.lang.psi.JetProperty import org.jetbrains.jet.lang.psi.JetProperty
import org.jetbrains.jet.lang.psi.JetFunction
import org.jetbrains.jet.lang.psi.JetClassOrObject
import org.jetbrains.jet.lang.psi.psiUtil.getParentByType
import org.jetbrains.jet.lang.psi.psiUtil.isExtensionDeclaration
import org.jetbrains.jet.lang.psi.JetPropertyAccessor
open class ExpressionVisitor(protected val converter: Converter, open class ExpressionVisitor(protected val converter: Converter,
private val usageReplacementMap: Map<PsiVariable, String> = mapOf()) : JavaElementVisitor() { private val usageReplacementMap: Map<PsiVariable, String> = mapOf()) : JavaElementVisitor() {
@@ -138,39 +143,73 @@ open class ExpressionVisitor(protected val converter: Converter,
} }
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) { override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
if (expression.isSuperConstructorCall() && expression.isInsidePrimaryConstructor()) return // skip it
val methodExpr = expression.getMethodExpression() val methodExpr = expression.getMethodExpression()
val target = methodExpr.resolve() val target = methodExpr.resolve()
if (target is KotlinLightMethod) { if (target is KotlinLightMethod) {
val property = target.origin as? JetProperty val origin = target.origin
if (property != null) { val isTopLevel = origin?.getParentByType(javaClass<JetClassOrObject>(), true) == null
if (origin is JetProperty || origin is JetPropertyAccessor) {
val property = if (origin is JetProperty) origin else origin.getParent() as JetProperty
val parameterCount = target.getParameterList().getParameters().size val parameterCount = target.getParameterList().getParameters().size
val argCount = expression.getArgumentList().getExpressions().size val arguments = expression.getArgumentList().getExpressions()
if (parameterCount == argCount) { if (parameterCount == arguments.size) {
val qualifier = converter.convertExpression(methodExpr.getQualifierExpression())
val propertyName = Identifier(property.getName()!!, false) val propertyName = Identifier(property.getName()!!, false)
val propertyAccess = QualifiedExpression(qualifier, propertyName) var isExtension = false
when(parameterCount) { val propertyAccess = if (isTopLevel) {
if (property.isExtensionDeclaration()) {
isExtension = true
QualifiedExpression(converter.convertExpression(arguments.firstOrNull()), propertyName)
}
else {
propertyName
}
}
else {
QualifiedExpression(converter.convertExpression(methodExpr.getQualifierExpression()), propertyName)
}
when(if (isExtension) parameterCount - 1 else parameterCount) {
0 /* getter */ -> { 0 /* getter */ -> {
result = propertyAccess result = propertyAccess
return return
} }
1 /* setter */ -> { 1 /* setter */ -> {
val argument = converter.convertExpression(expression.getArgumentList().getExpressions().single()) val argument = converter.convertExpression(arguments[if (isExtension) 1 else 0])
result = AssignmentExpression(propertyAccess, argument, "=") result = AssignmentExpression(propertyAccess, argument, "=")
return return
} }
} }
} }
} }
else if (origin is JetFunction) {
if (isTopLevel) {
result = if (origin.isExtensionDeclaration()) {
val arguments = expression.getArgumentList().getExpressions()
val qualifier = converter.convertExpression(arguments.firstOrNull())
MethodCallExpression(QualifiedExpression(qualifier, Identifier(origin.getName()!!, false)),
convertArguments(expression, isExtension = true),
converter.convertTypes(expression.getTypeArguments()),
converter.convertType(expression.getType()).isNullable)
}
else {
MethodCallExpression(Identifier(origin.getName()!!, false),
convertArguments(expression),
converter.convertTypes(expression.getTypeArguments()),
converter.convertType(expression.getType()).isNullable)
}
return
}
}
} }
if (!expression.isSuperConstructorCall() || !expression.isInsidePrimaryConstructor()) { result = MethodCallExpression(converter.convertExpression(methodExpr),
result = MethodCallExpression(converter.convertExpression(methodExpr), convertArguments(expression),
converter.convertArguments(expression), converter.convertTypes(expression.getTypeArguments()),
converter.convertTypes(expression.getTypeArguments()), converter.convertType(expression.getType()).isNullable)
converter.convertType(expression.getType()).isNullable)
}
} }
override fun visitNewExpression(expression: PsiNewExpression) { override fun visitNewExpression(expression: PsiNewExpression) {
@@ -194,7 +233,7 @@ open class ExpressionVisitor(protected val converter: Converter,
var arguments = argumentList?.getExpressions() ?: array() var arguments = argumentList?.getExpressions() ?: array()
if (constructor == null || constructor.isPrimaryConstructor() || isNotConvertedClass) { if (constructor == null || constructor.isPrimaryConstructor() || isNotConvertedClass) {
return NewClassExpression(converter.convertElement(classReference), return NewClassExpression(converter.convertElement(classReference),
converter.convertArguments(expression), convertArguments(expression),
converter.convertExpression(expression.getQualifier()), converter.convertExpression(expression.getQualifier()),
if (anonymousClass != null) converter.convertAnonymousClass(anonymousClass) else null) if (anonymousClass != null) converter.convertAnonymousClass(anonymousClass) else null)
} }
@@ -236,16 +275,15 @@ open class ExpressionVisitor(protected val converter: Converter,
} }
override fun visitReferenceExpression(expression: PsiReferenceExpression) { override fun visitReferenceExpression(expression: PsiReferenceExpression) {
val containingConstructor = expression.getContainingConstructor()
val insideSecondaryConstructor = containingConstructor != null && !containingConstructor.isPrimaryConstructor()
val addReceiver = insideSecondaryConstructor && (expression.getReference()?.resolve() as? PsiField)?.getContainingClass() == containingConstructor!!.getContainingClass()
val isNullable = converter.convertType(expression.getType(), expression.nullability()).isNullable val isNullable = converter.convertType(expression.getType(), expression.nullability()).isNullable
val referencedName = expression.getReferenceName()!! val referencedName = expression.getReferenceName()!!
var identifier: Expression = Identifier(referencedName, isNullable) var identifier: Expression = Identifier(referencedName, isNullable)
val qualifier = expression.getQualifierExpression() val qualifier = expression.getQualifierExpression()
if (addReceiver) { val containingConstructor = expression.getContainingConstructor()
val insideSecondaryConstructor = containingConstructor != null && !containingConstructor.isPrimaryConstructor()
if (insideSecondaryConstructor && (expression.getReference()?.resolve() as? PsiField)?.getContainingClass() == containingConstructor!!.getContainingClass()) {
identifier = QualifiedExpression(Identifier("__", false), Identifier(referencedName, isNullable)) identifier = QualifiedExpression(Identifier("__", false), Identifier(referencedName, isNullable))
} }
else if (insideSecondaryConstructor && expression.isThisConstructorCall()) { else if (insideSecondaryConstructor && expression.isThisConstructorCall()) {
@@ -269,7 +307,7 @@ open class ExpressionVisitor(protected val converter: Converter,
&& target.getContainingClass() != null && target.getContainingClass() != null
&& PsiTreeUtil.getParentOfType(expression, javaClass<PsiClass>()) != target.getContainingClass() && PsiTreeUtil.getParentOfType(expression, javaClass<PsiClass>()) != target.getContainingClass()
&& !isStaticallyImported(target, expression)) { && !isStaticallyImported(target, expression)) {
var member = target as PsiMember var member: PsiMember = target
var code = Identifier(referencedName).toKotlin() var code = Identifier(referencedName).toKotlin()
while (member.getContainingClass() != null) { while (member.getContainingClass() != null) {
code = Identifier(member.getContainingClass()!!.getName()!!).toKotlin() + "." + code code = Identifier(member.getContainingClass()!!.getName()!!).toKotlin() + "." + code
@@ -285,6 +323,7 @@ open class ExpressionVisitor(protected val converter: Converter,
identifier = Identifier(replacement, isNullable) identifier = Identifier(replacement, isNullable)
} }
} }
} }
result = QualifiedExpression(converter.convertExpression(qualifier), identifier) result = QualifiedExpression(converter.convertExpression(qualifier), identifier)
@@ -332,6 +371,23 @@ open class ExpressionVisitor(protected val converter: Converter,
result = PolyadicExpression(parameters, getOperatorString(expression.getOperationTokenType())) result = PolyadicExpression(parameters, getOperatorString(expression.getOperationTokenType()))
} }
private fun convertArguments(expression: PsiCallExpression, isExtension: Boolean = false): List<Expression> {
var arguments = expression.getArgumentList()?.getExpressions()?.toList() ?: listOf()
if (isExtension && arguments.isNotEmpty()) {
arguments = arguments.drop(1)
}
val resolved = expression.resolveMethod()
val expectedTypes = if (resolved != null)
resolved.getParameterList().getParameters().map { it.getType() }
else
listOf()
return if (arguments.size == expectedTypes.size())
(0..expectedTypes.lastIndex).map { i -> converter.convertExpression(arguments[i], expectedTypes[i]) }
else
arguments.map { converter.convertExpression(it) }
}
private fun getOperatorString(tokenType: IElementType): String { private fun getOperatorString(tokenType: IElementType): String {
return when(tokenType) { return when(tokenType) {
JavaTokenType.EQEQ -> "==" JavaTokenType.EQEQ -> "=="
@@ -1556,6 +1556,36 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("j2k/tests/testData/ast/kotlinApiAccess"), Pattern.compile("^(.+)\\.java$"), true); JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("j2k/tests/testData/ast/kotlinApiAccess"), Pattern.compile("^(.+)\\.java$"), true);
} }
@TestMetadata("ExtensionFunction.java")
public void testExtensionFunction() throws Exception {
doTest("j2k/tests/testData/ast/kotlinApiAccess/ExtensionFunction.java");
}
@TestMetadata("ExtensionProperty.java")
public void testExtensionProperty() throws Exception {
doTest("j2k/tests/testData/ast/kotlinApiAccess/ExtensionProperty.java");
}
@TestMetadata("GlobalFunction.java")
public void testGlobalFunction() throws Exception {
doTest("j2k/tests/testData/ast/kotlinApiAccess/GlobalFunction.java");
}
@TestMetadata("GlobalGenericFunction.java")
public void testGlobalGenericFunction() throws Exception {
doTest("j2k/tests/testData/ast/kotlinApiAccess/GlobalGenericFunction.java");
}
@TestMetadata("GlobalValue1.java")
public void testGlobalValue1() throws Exception {
doTest("j2k/tests/testData/ast/kotlinApiAccess/GlobalValue1.java");
}
@TestMetadata("GlobalValue2.java")
public void testGlobalValue2() throws Exception {
doTest("j2k/tests/testData/ast/kotlinApiAccess/GlobalValue2.java");
}
@TestMetadata("InheritedProperty.java") @TestMetadata("InheritedProperty.java")
public void testInheritedProperty() throws Exception { public void testInheritedProperty() throws Exception {
doTest("j2k/tests/testData/ast/kotlinApiAccess/InheritedProperty.java"); doTest("j2k/tests/testData/ast/kotlinApiAccess/InheritedProperty.java");
+16 -2
View File
@@ -1,5 +1,19 @@
package kotlinApi package kotlinApi
public open class KotlinClass { public open class KotlinClass {
public var property: String public var property: String = ""
} }
public fun globalFunction(s: String): String = s
public fun globalGenericFunction<T>(t: T): T = t
public fun Int.extensionFunction(): String = toString()
public var globalValue1: Int = 1
public var globalValue2: Int
get() = 0
set(value) {}
public var String.extensionProperty: Int
get() = 1
set(value) {}
@@ -0,0 +1,8 @@
//file
import kotlinApi.KotlinApiPackage;
class C {
void foo() {
String s = KotlinApiPackage.extensionFunction(1);
}
}
@@ -0,0 +1,7 @@
import kotlinApi.*
class C() {
fun foo() {
val s = 1.extensionFunction()
}
}
@@ -0,0 +1,9 @@
//file
import kotlinApi.KotlinApiPackage;
class C {
int foo() {
KotlinApiPackage.setExtensionProperty("a", 1);
return KotlinApiPackage.getExtensionProperty("b");
}
}
@@ -0,0 +1,8 @@
import kotlinApi.*
class C() {
fun foo(): Int {
"a".extensionProperty = 1
return "b".extensionProperty
}
}
@@ -0,0 +1,8 @@
//file
import kotlinApi.KotlinApiPackage;
class C {
void foo() {
String s = KotlinApiPackage.globalFunction("x");
}
}
@@ -0,0 +1,7 @@
import kotlinApi.*
class C() {
fun foo() {
val s = globalFunction("x")
}
}
@@ -0,0 +1,8 @@
//file
import kotlinApi.KotlinApiPackage;
class C {
void foo() {
int v = KotlinApiPackage.<Integer>globalGenericFunction(1);
}
}
@@ -0,0 +1,7 @@
import kotlinApi.*
class C() {
fun foo() {
val v = globalGenericFunction<Int>(1)!!
}
}
@@ -0,0 +1,9 @@
//file
import kotlinApi.KotlinApiPackage;
class C {
int foo() {
KotlinApiPackage.setGlobalValue1(0)
return KotlinApiPackage.getGlobalValue1();
}
}
@@ -0,0 +1,8 @@
import kotlinApi.*
class C() {
fun foo(): Int {
globalValue1 = 0
return globalValue1
}
}
@@ -0,0 +1,9 @@
//file
import kotlinApi.KotlinApiPackage;
class C {
int foo() {
KotlinApiPackage.setGlobalValue2(0)
return KotlinApiPackage.getGlobalValue2();
}
}
@@ -0,0 +1,8 @@
import kotlinApi.*
class C() {
fun foo(): Int {
globalValue2 = 0
return globalValue2
}
}