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="library" name="idea-full" level="project" />
<orderEntry type="module" module-name="frontend.java" />
<orderEntry type="module" module-name="jet.as.java.psi" />
</component>
</module>
+1 -15
View File
@@ -58,7 +58,7 @@ public class Converter(val project: Project, val settings: ConverterSettings) {
is PsiExpression -> convertExpression(element)
is PsiComment -> Comment(element.getText()!!)
is PsiImportList -> convertImportList(element)
is PsiImportStatementBase -> convertImport(element)
is PsiImportStatementBase -> convertImport(element, false)
is PsiPackageStatement -> PackageStatement(quoteKeywords(element.getPackageName() ?: ""))
is PsiWhiteSpace -> WhiteSpace(element.getText()!!)
else -> null
@@ -615,20 +615,6 @@ public class Converter(val project: Project, val settings: ConverterSettings) {
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 {
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 org.jetbrains.jet.lang.resolve.name.FqName
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 {
override fun toKotlin() = "import " + name
}
class ImportList(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()
}
}
class ImportList(private val imports: List<Import>) : Element {
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 =
ImportList(importList.getAllImportStatements() map { convertImport(it) })
public fun Converter.convertImportList(importList: PsiImportList): ImportList =
ImportList(importList.getAllImportStatements().map { convertImport(it, true) }.filterNotNull())
fun Converter.convertImport(i: PsiImportStatementBase): Import {
val reference = i.getImportReference()
if (reference != null) {
return Import(quoteKeywords(reference.getQualifiedName()!!) + if (i.isOnDemand()) ".*" else "")
public fun Converter.convertImport(anImport: PsiImportStatementBase, filter: Boolean): Import? {
val reference = anImport.getImportReference()
if (reference == null) return null
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.lang.resolve.java.jetAsJava.KotlinLightMethod
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,
private val usageReplacementMap: Map<PsiVariable, String> = mapOf()) : JavaElementVisitor() {
@@ -138,39 +143,73 @@ open class ExpressionVisitor(protected val converter: Converter,
}
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
if (expression.isSuperConstructorCall() && expression.isInsidePrimaryConstructor()) return // skip it
val methodExpr = expression.getMethodExpression()
val target = methodExpr.resolve()
if (target is KotlinLightMethod) {
val property = target.origin as? JetProperty
if (property != null) {
val origin = target.origin
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 argCount = expression.getArgumentList().getExpressions().size
if (parameterCount == argCount) {
val qualifier = converter.convertExpression(methodExpr.getQualifierExpression())
val arguments = expression.getArgumentList().getExpressions()
if (parameterCount == arguments.size) {
val propertyName = Identifier(property.getName()!!, false)
val propertyAccess = QualifiedExpression(qualifier, propertyName)
when(parameterCount) {
var isExtension = false
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 */ -> {
result = propertyAccess
return
}
1 /* setter */ -> {
val argument = converter.convertExpression(expression.getArgumentList().getExpressions().single())
val argument = converter.convertExpression(arguments[if (isExtension) 1 else 0])
result = AssignmentExpression(propertyAccess, argument, "=")
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),
converter.convertArguments(expression),
converter.convertTypes(expression.getTypeArguments()),
converter.convertType(expression.getType()).isNullable)
}
result = MethodCallExpression(converter.convertExpression(methodExpr),
convertArguments(expression),
converter.convertTypes(expression.getTypeArguments()),
converter.convertType(expression.getType()).isNullable)
}
override fun visitNewExpression(expression: PsiNewExpression) {
@@ -194,7 +233,7 @@ open class ExpressionVisitor(protected val converter: Converter,
var arguments = argumentList?.getExpressions() ?: array()
if (constructor == null || constructor.isPrimaryConstructor() || isNotConvertedClass) {
return NewClassExpression(converter.convertElement(classReference),
converter.convertArguments(expression),
convertArguments(expression),
converter.convertExpression(expression.getQualifier()),
if (anonymousClass != null) converter.convertAnonymousClass(anonymousClass) else null)
}
@@ -236,16 +275,15 @@ open class ExpressionVisitor(protected val converter: Converter,
}
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 referencedName = expression.getReferenceName()!!
var identifier: Expression = Identifier(referencedName, isNullable)
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))
}
else if (insideSecondaryConstructor && expression.isThisConstructorCall()) {
@@ -269,7 +307,7 @@ open class ExpressionVisitor(protected val converter: Converter,
&& target.getContainingClass() != null
&& PsiTreeUtil.getParentOfType(expression, javaClass<PsiClass>()) != target.getContainingClass()
&& !isStaticallyImported(target, expression)) {
var member = target as PsiMember
var member: PsiMember = target
var code = Identifier(referencedName).toKotlin()
while (member.getContainingClass() != null) {
code = Identifier(member.getContainingClass()!!.getName()!!).toKotlin() + "." + code
@@ -285,6 +323,7 @@ open class ExpressionVisitor(protected val converter: Converter,
identifier = Identifier(replacement, isNullable)
}
}
}
result = QualifiedExpression(converter.convertExpression(qualifier), identifier)
@@ -332,6 +371,23 @@ open class ExpressionVisitor(protected val converter: Converter,
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 {
return when(tokenType) {
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);
}
@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")
public void testInheritedProperty() throws Exception {
doTest("j2k/tests/testData/ast/kotlinApiAccess/InheritedProperty.java");
+16 -2
View File
@@ -1,5 +1,19 @@
package kotlinApi
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
}
}