diff --git a/j2k/j2k.iml b/j2k/j2k.iml index a5e5d67ecd7..2eb792263d9 100644 --- a/j2k/j2k.iml +++ b/j2k/j2k.iml @@ -12,6 +12,7 @@ + diff --git a/j2k/src/org/jetbrains/jet/j2k/Converter.kt b/j2k/src/org/jetbrains/jet/j2k/Converter.kt index aa4fd8fd8d8..794881d1478 100644 --- a/j2k/src/org/jetbrains/jet/j2k/Converter.kt +++ b/j2k/src/org/jetbrains/jet/j2k/Converter.kt @@ -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 { - 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 diff --git a/j2k/src/org/jetbrains/jet/j2k/ast/Imports.kt b/j2k/src/org/jetbrains/jet/j2k/ast/Imports.kt index 09aeb915730..e06faa4fa56 100644 --- a/j2k/src/org/jetbrains/jet/j2k/ast/Imports.kt +++ b/j2k/src/org/jetbrains/jet/j2k/ast/Imports.kt @@ -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) : 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) : 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("") -} \ No newline at end of file + 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 +} diff --git a/j2k/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.kt b/j2k/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.kt index 93ce24e3765..ef5f3c70b3a 100644 --- a/j2k/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.kt +++ b/j2k/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.kt @@ -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 = 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(), 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()) != 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 { + 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 -> "==" diff --git a/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java b/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java index 3dcbfc9bb01..3650d0e49f9 100644 --- a/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java +++ b/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java @@ -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"); diff --git a/j2k/tests/testData/KotlinApi.kt b/j2k/tests/testData/KotlinApi.kt index 2e026a7240e..098ec57d4f6 100644 --- a/j2k/tests/testData/KotlinApi.kt +++ b/j2k/tests/testData/KotlinApi.kt @@ -1,5 +1,19 @@ package kotlinApi public open class KotlinClass { - public var property: String -} \ No newline at end of file + public var property: String = "" +} + +public fun globalFunction(s: String): String = s +public fun globalGenericFunction(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) {} \ No newline at end of file diff --git a/j2k/tests/testData/ast/kotlinApiAccess/ExtensionFunction.java b/j2k/tests/testData/ast/kotlinApiAccess/ExtensionFunction.java new file mode 100644 index 00000000000..7dcded5dde4 --- /dev/null +++ b/j2k/tests/testData/ast/kotlinApiAccess/ExtensionFunction.java @@ -0,0 +1,8 @@ +//file +import kotlinApi.KotlinApiPackage; + +class C { + void foo() { + String s = KotlinApiPackage.extensionFunction(1); + } +} diff --git a/j2k/tests/testData/ast/kotlinApiAccess/ExtensionFunction.kt b/j2k/tests/testData/ast/kotlinApiAccess/ExtensionFunction.kt new file mode 100644 index 00000000000..058b88e1dd0 --- /dev/null +++ b/j2k/tests/testData/ast/kotlinApiAccess/ExtensionFunction.kt @@ -0,0 +1,7 @@ +import kotlinApi.* + +class C() { + fun foo() { + val s = 1.extensionFunction() + } +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/kotlinApiAccess/ExtensionProperty.java b/j2k/tests/testData/ast/kotlinApiAccess/ExtensionProperty.java new file mode 100644 index 00000000000..287bf3b0445 --- /dev/null +++ b/j2k/tests/testData/ast/kotlinApiAccess/ExtensionProperty.java @@ -0,0 +1,9 @@ +//file +import kotlinApi.KotlinApiPackage; + +class C { + int foo() { + KotlinApiPackage.setExtensionProperty("a", 1); + return KotlinApiPackage.getExtensionProperty("b"); + } +} diff --git a/j2k/tests/testData/ast/kotlinApiAccess/ExtensionProperty.kt b/j2k/tests/testData/ast/kotlinApiAccess/ExtensionProperty.kt new file mode 100644 index 00000000000..c6a11c8bddb --- /dev/null +++ b/j2k/tests/testData/ast/kotlinApiAccess/ExtensionProperty.kt @@ -0,0 +1,8 @@ +import kotlinApi.* + +class C() { + fun foo(): Int { + "a".extensionProperty = 1 + return "b".extensionProperty + } +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/kotlinApiAccess/GlobalFunction.java b/j2k/tests/testData/ast/kotlinApiAccess/GlobalFunction.java new file mode 100644 index 00000000000..127088a30a3 --- /dev/null +++ b/j2k/tests/testData/ast/kotlinApiAccess/GlobalFunction.java @@ -0,0 +1,8 @@ +//file +import kotlinApi.KotlinApiPackage; + +class C { + void foo() { + String s = KotlinApiPackage.globalFunction("x"); + } +} diff --git a/j2k/tests/testData/ast/kotlinApiAccess/GlobalFunction.kt b/j2k/tests/testData/ast/kotlinApiAccess/GlobalFunction.kt new file mode 100644 index 00000000000..dfd478dfe49 --- /dev/null +++ b/j2k/tests/testData/ast/kotlinApiAccess/GlobalFunction.kt @@ -0,0 +1,7 @@ +import kotlinApi.* + +class C() { + fun foo() { + val s = globalFunction("x") + } +} diff --git a/j2k/tests/testData/ast/kotlinApiAccess/GlobalGenericFunction.java b/j2k/tests/testData/ast/kotlinApiAccess/GlobalGenericFunction.java new file mode 100644 index 00000000000..be75de65a3a --- /dev/null +++ b/j2k/tests/testData/ast/kotlinApiAccess/GlobalGenericFunction.java @@ -0,0 +1,8 @@ +//file +import kotlinApi.KotlinApiPackage; + +class C { + void foo() { + int v = KotlinApiPackage.globalGenericFunction(1); + } +} diff --git a/j2k/tests/testData/ast/kotlinApiAccess/GlobalGenericFunction.kt b/j2k/tests/testData/ast/kotlinApiAccess/GlobalGenericFunction.kt new file mode 100644 index 00000000000..1962cd451ea --- /dev/null +++ b/j2k/tests/testData/ast/kotlinApiAccess/GlobalGenericFunction.kt @@ -0,0 +1,7 @@ +import kotlinApi.* + +class C() { + fun foo() { + val v = globalGenericFunction(1)!! + } +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/kotlinApiAccess/GlobalValue1.java b/j2k/tests/testData/ast/kotlinApiAccess/GlobalValue1.java new file mode 100644 index 00000000000..2135ee3ab95 --- /dev/null +++ b/j2k/tests/testData/ast/kotlinApiAccess/GlobalValue1.java @@ -0,0 +1,9 @@ +//file +import kotlinApi.KotlinApiPackage; + +class C { + int foo() { + KotlinApiPackage.setGlobalValue1(0) + return KotlinApiPackage.getGlobalValue1(); + } +} diff --git a/j2k/tests/testData/ast/kotlinApiAccess/GlobalValue1.kt b/j2k/tests/testData/ast/kotlinApiAccess/GlobalValue1.kt new file mode 100644 index 00000000000..aa88eef973f --- /dev/null +++ b/j2k/tests/testData/ast/kotlinApiAccess/GlobalValue1.kt @@ -0,0 +1,8 @@ +import kotlinApi.* + +class C() { + fun foo(): Int { + globalValue1 = 0 + return globalValue1 + } +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/kotlinApiAccess/GlobalValue2.java b/j2k/tests/testData/ast/kotlinApiAccess/GlobalValue2.java new file mode 100644 index 00000000000..81ebe699755 --- /dev/null +++ b/j2k/tests/testData/ast/kotlinApiAccess/GlobalValue2.java @@ -0,0 +1,9 @@ +//file +import kotlinApi.KotlinApiPackage; + +class C { + int foo() { + KotlinApiPackage.setGlobalValue2(0) + return KotlinApiPackage.getGlobalValue2(); + } +} diff --git a/j2k/tests/testData/ast/kotlinApiAccess/GlobalValue2.kt b/j2k/tests/testData/ast/kotlinApiAccess/GlobalValue2.kt new file mode 100644 index 00000000000..84e96eceb3e --- /dev/null +++ b/j2k/tests/testData/ast/kotlinApiAccess/GlobalValue2.kt @@ -0,0 +1,8 @@ +import kotlinApi.* + +class C() { + fun foo(): Int { + globalValue2 = 0 + return globalValue2 + } +} \ No newline at end of file