diff --git a/j2k/src/org/jetbrains/jet/j2k/ConstructorConverter.kt b/j2k/src/org/jetbrains/jet/j2k/ConstructorConverter.kt index 1aabd44ea45..bd23239ff62 100644 --- a/j2k/src/org/jetbrains/jet/j2k/ConstructorConverter.kt +++ b/j2k/src/org/jetbrains/jet/j2k/ConstructorConverter.kt @@ -183,8 +183,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter var body = postProcessBody(bodyConverter.convertBlock(constructor.getBody())) val containingClass = constructor.getContainingClass() val typeParameterList = converter.convertTypeParameterList(containingClass?.getTypeParameterList()) - val factoryFunctionType = ClassType(containingClass?.declarationIdentifier() ?: Identifier.Empty, - typeParameterList.parameters, + val factoryFunctionType = ClassType(ReferenceElement(containingClass?.declarationIdentifier() ?: Identifier.Empty, typeParameterList.parameters).assignNoPrototype(), Nullability.NotNull, converter.settings).assignNoPrototype() return FactoryFunction(constructor.declarationIdentifier(), annotations, correctFactoryFunctionAccess(modifiers), @@ -201,13 +200,14 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter val body = primaryConstructor.getBody() val parameterUsageReplacementMap = HashMap() + val correctedTypeConverter = converter.withSpecialContext(psiClass).typeConverter /* to correct nested class references */ val block = if (body != null) { val statementsToRemove = HashSet() for (parameter in params) { val (field, initializationStatement) = findBackingFieldForConstructorParameter(parameter, primaryConstructor) ?: continue - val fieldType = typeConverter.convertVariableType(field) - val parameterType = typeConverter.convertVariableType(parameter) + val fieldType = correctedTypeConverter.convertVariableType(field) + val parameterType = correctedTypeConverter.convertVariableType(parameter) // types can be different only in nullability val `type` = if (fieldType == parameterType) { fieldType @@ -244,6 +244,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter // we need to replace renamed parameter usages in base class constructor arguments and in default values val correctedConverter = converter.withExpressionVisitor { ReplacingExpressionVisitor(this, parameterUsageReplacementMap, it) } + .withSpecialContext(psiClass) /* to correct nested class references */ val statement = primaryConstructor.getBody()?.getStatements()?.firstOrNull() val methodCall = (statement as? PsiExpressionStatement)?.getExpression() as? PsiMethodCallExpression @@ -259,7 +260,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter else null if (!parameterToField.containsKey(parameter)) { - converter.convertParameter(parameter, defaultValue = defaultValue) + correctedConverter.convertParameter(parameter, defaultValue = defaultValue) } else { val (field, `type`) = parameterToField[parameter]!! diff --git a/j2k/src/org/jetbrains/jet/j2k/Converter.kt b/j2k/src/org/jetbrains/jet/j2k/Converter.kt index 62cfeb92dde..fdab1aa2afe 100644 --- a/j2k/src/org/jetbrains/jet/j2k/Converter.kt +++ b/j2k/src/org/jetbrains/jet/j2k/Converter.kt @@ -24,6 +24,7 @@ import com.intellij.psi.CommonClassNames.* import org.jetbrains.jet.lang.types.expressions.OperatorConventions.* import com.intellij.openapi.project.Project import com.intellij.psi.util.PsiMethodUtil +import com.intellij.psi.util.PsiTreeUtil public trait ConversionScope { public fun contains(element: PsiElement): Boolean @@ -34,29 +35,53 @@ public class FilesConversionScope(val files: Collection) : Conversi } public class Converter private(val project: Project, val settings: ConverterSettings, val conversionScope: ConversionScope, val state: Converter.State) { - private class State(val typeConverter: TypeConverter, - val methodReturnType: PsiType?, + private class State(val methodReturnType: PsiType?, val expressionVisitorFactory: (Converter) -> ExpressionVisitor, - val statementVisitorFactory: (Converter) -> StatementVisitor) - val typeConverter: TypeConverter = state.typeConverter + val statementVisitorFactory: (Converter) -> StatementVisitor, + val specialContext: PsiElement?, + val importList: ImportList?, + val importsToAdd: MutableCollection?) + + val typeConverter: TypeConverter = TypeConverter(this) + val methodReturnType: PsiType? = state.methodReturnType + val specialContext: PsiElement? = state.specialContext + val importNames: Set = state.importList?.imports?.mapTo(HashSet()) { it.name } ?: setOf() + val importsToAdd: MutableCollection? = state.importsToAdd private val expressionVisitor = state.expressionVisitorFactory(this) private val statementVisitor = state.statementVisitorFactory(this) class object { - public fun create(project: Project, settings: ConverterSettings, conversionScope: ConversionScope): Converter - = Converter(project, settings, conversionScope, State(TypeConverter(settings, conversionScope), null, { ExpressionVisitor(it) }, { StatementVisitor(it) })) + public fun create(project: Project, settings: ConverterSettings, conversionScope: ConversionScope): Converter { + val state = State(null, { ExpressionVisitor(it) }, { StatementVisitor(it) }, null, null, null) + return Converter(project, settings, conversionScope, state) + } } fun withMethodReturnType(methodReturnType: PsiType?): Converter - = Converter(project, settings, conversionScope, State(typeConverter, methodReturnType, state.expressionVisitorFactory, state.statementVisitorFactory)) + = Converter(project, settings, conversionScope, + State(methodReturnType, state.expressionVisitorFactory, state.statementVisitorFactory, state.specialContext, state.importList, state.importsToAdd)) fun withExpressionVisitor(factory: (Converter) -> ExpressionVisitor): Converter - = Converter(project, settings, conversionScope, State(typeConverter, state.methodReturnType, factory, state.statementVisitorFactory)) + = Converter(project, settings, conversionScope, + State(state.methodReturnType, factory, state.statementVisitorFactory, state.specialContext, state.importList, state.importsToAdd)) fun withStatementVisitor(factory: (Converter) -> StatementVisitor): Converter - = Converter(project, settings, conversionScope, State(typeConverter, state.methodReturnType, state.expressionVisitorFactory, factory)) + = Converter(project, settings, conversionScope, + State(state.methodReturnType, state.expressionVisitorFactory, factory, state.specialContext, state.importList, state.importsToAdd)) + + fun withSpecialContext(context: PsiElement): Converter + = Converter(project, settings, conversionScope, + State(state.methodReturnType, state.expressionVisitorFactory, state.statementVisitorFactory, context, state.importList, state.importsToAdd)) + + private fun withImportList(importList: ImportList): Converter + = Converter(project, settings, conversionScope, + State(state.methodReturnType, state.expressionVisitorFactory, state.statementVisitorFactory, state.specialContext, importList, state.importsToAdd)) + + private fun withImportsToAdd(importsToAdd: MutableCollection): Converter + = Converter(project, settings, conversionScope, + State(state.methodReturnType, state.expressionVisitorFactory, state.statementVisitorFactory, state.specialContext, state.importList, importsToAdd)) public fun elementToKotlin(element: PsiElement): String { val converted = convertTopElement(element) ?: return "" @@ -80,21 +105,22 @@ public class Converter private(val project: Project, val settings: ConverterSett } private fun convertFile(javaFile: PsiJavaFile): File { + val importsToAdd = LinkedHashSet() + var converter = this.withImportsToAdd(importsToAdd) var convertedChildren = javaFile.getChildren().map { if (it is PsiImportList) { val importList = convertImportList(it) - typeConverter.importList = importList + converter = converter.withImportList(importList) importList } else { - convertTopElement(it) + converter.convertTopElement(it) } }.filterNotNull() - typeConverter.importList = null - if (typeConverter.importsToAdd.isNotEmpty()) { + if (importsToAdd.isNotEmpty()) { val importList = convertedChildren.filterIsInstance(javaClass()).first() - val newImportList = ImportList(importList.imports + typeConverter.importsToAdd).assignPrototypesFrom(importList) + val newImportList = ImportList(importList.imports + importsToAdd.map { Import(it).assignNoPrototype() }).assignPrototypesFrom(importList) convertedChildren = convertedChildren.map { if (it == importList) newImportList else it } } @@ -392,6 +418,7 @@ public class Converter private(val project: Project, val settings: ConverterSett return expressionVisitor.result.assignPrototype(expression) } + //TODO: drop this method - it has unclear semantics fun convertElement(element: PsiElement?): Element { if (element == null) return Element.Empty @@ -400,6 +427,46 @@ public class Converter private(val project: Project, val settings: ConverterSett return elementVisitor.result.assignPrototype(element) } + fun convertCodeReferenceElement(element: PsiJavaCodeReferenceElement, hasExternalQualifier: Boolean, typeArgsConverted: List? = null): ReferenceElement { + val typeArgs = typeArgsConverted ?: typeConverter.convertTypes(element.getTypeParameters()) + + if (element.isQualified()) { + var result = Identifier.toKotlin(element.getReferenceName()!!) + var qualifier = element.getQualifier() + while (qualifier != null) { + val codeRefElement = qualifier as PsiJavaCodeReferenceElement + result = Identifier.toKotlin(codeRefElement.getReferenceName()!!) + "." + result + qualifier = codeRefElement.getQualifier() + } + return ReferenceElement(Identifier(result).assignNoPrototype(), typeArgs).assignPrototype(element) + } + else { + if (!hasExternalQualifier) { + // references to nested classes may need correction + val targetClass = element.resolve() as? PsiClass + if (targetClass != null) { + val identifier = constructNestedClassReferenceIdentifier(targetClass, specialContext ?: element) + if (identifier != null) { + return ReferenceElement(identifier, typeArgs).assignPrototype(element) + } + } + } + + return ReferenceElement(Identifier(element.getReferenceName()!!).assignNoPrototype(), typeArgs).assignPrototype(element) + } + } + + private fun constructNestedClassReferenceIdentifier(psiClass: PsiClass, context: PsiElement): Identifier? { + val outerClass = psiClass.getContainingClass() + if (outerClass != null + && !PsiTreeUtil.isAncestor(outerClass, context, true) + && !psiClass.isImported(context.getContainingFile() as PsiJavaFile)) { + val qualifier = constructNestedClassReferenceIdentifier(outerClass, context)?.name ?: outerClass.getName()!! + return Identifier(Identifier.toKotlin(qualifier) + "." + Identifier.toKotlin(psiClass.getName()!!)).assignNoPrototype() + } + return null + } + fun convertTypeElement(element: PsiTypeElement?): TypeElement = TypeElement(if (element == null) ErrorType().assignNoPrototype() else typeConverter.convertType(element.getType())).assignPrototype(element) diff --git a/j2k/src/org/jetbrains/jet/j2k/TypeConverter.kt b/j2k/src/org/jetbrains/jet/j2k/TypeConverter.kt index 683d327f3a4..023bad75cdf 100644 --- a/j2k/src/org/jetbrains/jet/j2k/TypeConverter.kt +++ b/j2k/src/org/jetbrains/jet/j2k/TypeConverter.kt @@ -31,27 +31,16 @@ import org.jetbrains.jet.j2k.ast.ErrorType import com.intellij.codeInsight.NullableNotNullManager import org.jetbrains.jet.j2k.ast.ArrayType import org.jetbrains.jet.j2k.ast.ClassType +import org.jetbrains.jet.j2k.ast.ReferenceElement import org.jetbrains.jet.j2k.ast.Identifier -class TypeConverter(val settings: ConverterSettings, val conversionScope: ConversionScope) { +class TypeConverter(val converter: Converter) { private val nullabilityCache = HashMap() - private val classesToImport = HashSet() - - public var importList: ImportList? = null - set(value) { - $importList = value - importNames = importList?.imports?.mapTo(HashSet()) { it.name } ?: setOf() - - } - private var importNames: Set = setOf() - - public val importsToAdd: Collection - get() = classesToImport.map { Import(it).assignNoPrototype() } public fun convertType(`type`: PsiType?, nullability: Nullability = Nullability.Default): Type { if (`type` == null) return ErrorType().assignNoPrototype() - val result = `type`.accept(TypeVisitor(this, importNames, classesToImport))!!.assignNoPrototype() + val result = `type`.accept(TypeVisitor(converter))!!.assignNoPrototype() return when (nullability) { Nullability.NotNull -> result.toNotNullType() Nullability.Nullable -> result.toNullableType() @@ -64,9 +53,9 @@ class TypeConverter(val settings: ConverterSettings, val conversionScope: Conver public fun convertVariableType(variable: PsiVariable): Type { val result = if (variable.isMainMethodParameter()) { - ArrayType(ClassType(Identifier("String").assignNoPrototype(), listOf(), Nullability.NotNull, settings).assignNoPrototype(), + ArrayType(ClassType(ReferenceElement(Identifier("String").assignNoPrototype(), listOf()).assignNoPrototype(), Nullability.NotNull, converter.settings).assignNoPrototype(), Nullability.NotNull, - settings) + converter.settings).assignNoPrototype() } else { convertType(variable.getType(), variableNullability(variable)) @@ -124,7 +113,7 @@ class TypeConverter(val settings: ConverterSettings, val conversionScope: Conver return Nullability.NotNull } - if (!conversionScope.contains(variable)) { // do not analyze usages out of our conversion scope + if (!converter.conversionScope.contains(variable)) { // do not analyze usages out of our conversion scope if (variable is PsiParameter) { // Object.equals corresponds to Any.equals which has nullable parameter: val scope = variable.getDeclarationScope() @@ -203,7 +192,7 @@ class TypeConverter(val settings: ConverterSettings, val conversionScope: Conver return Nullability.Nullable } - if (!conversionScope.contains(method)) return nullability // do not analyze body and usages of methods out of our conversion scope + if (!converter.conversionScope.contains(method)) return nullability // do not analyze body and usages of methods out of our conversion scope if (nullability == Nullability.Default) { method.getBody()?.accept(object: JavaRecursiveElementVisitor() { diff --git a/j2k/src/org/jetbrains/jet/j2k/Utils.kt b/j2k/src/org/jetbrains/jet/j2k/Utils.kt index 4d230d79981..9f1cdf95918 100644 --- a/j2k/src/org/jetbrains/jet/j2k/Utils.kt +++ b/j2k/src/org/jetbrains/jet/j2k/Utils.kt @@ -139,3 +139,20 @@ fun PsiMethod.isMainMethod(): Boolean = PsiMethodUtil.isMainMethod(this) fun List.singleOrNull2(): T? = if (size == 1) this[0] else null fun Array.singleOrNull2(): T? = if (size == 1) this[0] else null + +fun PsiMember.isImported(file: PsiJavaFile): Boolean { + if (this is PsiClass) { + val fqName = getQualifiedName() + val index = fqName?.lastIndexOf('.') ?: -1 + val parentName = if (index >= 0) fqName!!.substring(0, index) else null + return file.getImportList()?.getAllImportStatements()?.any { + it.getImportReference()?.getQualifiedName() == (if (it.isOnDemand()) parentName else fqName) + } ?: false + } + else { + return getContainingClass() != null && file.getImportList()?.getImportStaticStatements()?.any { + it.resolveTargetClass() == getContainingClass() && (it.isOnDemand() || it.getReferenceName() == getName()) + } ?: false + } +} + diff --git a/j2k/src/org/jetbrains/jet/j2k/ast/NewClassExpression.kt b/j2k/src/org/jetbrains/jet/j2k/ast/NewClassExpression.kt index bf4b77386c8..0633e3b466f 100644 --- a/j2k/src/org/jetbrains/jet/j2k/ast/NewClassExpression.kt +++ b/j2k/src/org/jetbrains/jet/j2k/ast/NewClassExpression.kt @@ -19,7 +19,7 @@ package org.jetbrains.jet.j2k.ast import org.jetbrains.jet.j2k.* class NewClassExpression( - val name: Element, + val name: ReferenceElement?, val arguments: List, val qualifier: Expression = Expression.Empty, val anonymousClass: AnonymousClassBody? = null @@ -34,7 +34,9 @@ class NewClassExpression( builder.append(qualifier).append(if (qualifier.isNullable) "!!." else ".") } - builder.append(name) + if (name != null) { + builder.append(name) + } if (anonymousClass == null || !anonymousClass.extendsTrait) { builder.append("(").append(arguments, ", ").append(")") diff --git a/j2k/src/org/jetbrains/jet/j2k/ast/ReferenceElement.kt b/j2k/src/org/jetbrains/jet/j2k/ast/ReferenceElement.kt index 9e03f71646f..ea8a8f8f8d3 100644 --- a/j2k/src/org/jetbrains/jet/j2k/ast/ReferenceElement.kt +++ b/j2k/src/org/jetbrains/jet/j2k/ast/ReferenceElement.kt @@ -18,8 +18,8 @@ package org.jetbrains.jet.j2k.ast import org.jetbrains.jet.j2k.* -class ReferenceElement(val reference: Identifier, val types: List) : Element() { +class ReferenceElement(val name: Identifier, val typeArgs: List) : Element() { override fun generateCode(builder: CodeBuilder) { - builder.append(reference).append(types, ", ", "<", ">") + builder.append(name).append(typeArgs, ", ", "<", ">") } } diff --git a/j2k/src/org/jetbrains/jet/j2k/ast/Types.kt b/j2k/src/org/jetbrains/jet/j2k/ast/Types.kt index 1e4e6fc71c2..cb58efc14be 100644 --- a/j2k/src/org/jetbrains/jet/j2k/ast/Types.kt +++ b/j2k/src/org/jetbrains/jet/j2k/ast/Types.kt @@ -78,15 +78,15 @@ class ErrorType : NotNullType() { } } -class ClassType(val name: Identifier, val typeArgs: List, nullability: Nullability, settings: ConverterSettings) +class ClassType(val referenceElement: ReferenceElement, nullability: Nullability, settings: ConverterSettings) : MayBeNullableType(nullability, settings) { override fun generateCode(builder: CodeBuilder) { - builder.append(name).append(typeArgs, ", ", "<", ">").append(isNullableStr) + builder append referenceElement append isNullableStr } - override fun toNotNullType(): Type = ClassType(name, typeArgs, Nullability.NotNull, settings).assignPrototypesFrom(this) - override fun toNullableType(): Type = ClassType(name, typeArgs, Nullability.Nullable, settings).assignPrototypesFrom(this) + override fun toNotNullType(): Type = ClassType(referenceElement, Nullability.NotNull, settings).assignPrototypesFrom(this) + override fun toNullableType(): Type = ClassType(referenceElement, Nullability.Nullable, settings).assignPrototypesFrom(this) } class ArrayType(val elementType: Type, nullability: Nullability, settings: ConverterSettings) diff --git a/j2k/src/org/jetbrains/jet/j2k/visitors/ElementVisitor.kt b/j2k/src/org/jetbrains/jet/j2k/visitors/ElementVisitor.kt index 6f5d0bab88c..2e971a8196d 100644 --- a/j2k/src/org/jetbrains/jet/j2k/visitors/ElementVisitor.kt +++ b/j2k/src/org/jetbrains/jet/j2k/visitors/ElementVisitor.kt @@ -42,23 +42,6 @@ class ElementVisitor(private val converter: Converter) : JavaElementVisitor() { result = ExpressionList(converter.convertExpressions(list.getExpressions())) } - override fun visitReferenceElement(reference: PsiJavaCodeReferenceElement) { - val types = typeConverter.convertTypes(reference.getTypeParameters()) - if (!reference.isQualified()) { - result = ReferenceElement(Identifier(reference.getReferenceName()!!).assignNoPrototype(), types) - } - else { - var code = Identifier.toKotlin(reference.getReferenceName()!!) - var qualifier = reference.getQualifier() - while (qualifier != null) { - val p = qualifier as PsiJavaCodeReferenceElement - code = Identifier.toKotlin(p.getReferenceName()!!) + "." + code - qualifier = p.getQualifier() - } - result = ReferenceElement(Identifier(code).assignNoPrototype(), types) - } - } - override fun visitTypeElement(`type`: PsiTypeElement) { result = TypeElement(typeConverter.convertType(`type`.getType())) } diff --git a/j2k/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.kt b/j2k/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.kt index 22d42e06c31..63ccb01eadc 100644 --- a/j2k/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.kt +++ b/j2k/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.kt @@ -254,19 +254,17 @@ open class ExpressionVisitor(private val converter: Converter) : JavaElementVisi converter.convertExpressions(expression.getArrayDimensions())) } else { - result = createNewClassExpression(expression) + val anonymousClass = expression.getAnonymousClass() + val qualifier = expression.getQualifier() + val classRef = expression.getClassOrAnonymousClassReference() + val classRefConverted = if (classRef != null) converter.convertCodeReferenceElement(classRef, hasExternalQualifier = qualifier != null) else null + result = NewClassExpression(classRefConverted, + convertArguments(expression), + converter.convertExpression(qualifier), + if (anonymousClass != null) converter.convertAnonymousClassBody(anonymousClass) else null) } } - private fun createNewClassExpression(expression: PsiNewExpression): Expression { - val anonymousClass = expression.getAnonymousClass() - val classReference = expression.getClassOrAnonymousClassReference() - return NewClassExpression(converter.convertElement(classReference), - convertArguments(expression), - converter.convertExpression(expression.getQualifier()), - if (anonymousClass != null) converter.convertAnonymousClassBody(anonymousClass) else null) - } - override fun visitParenthesizedExpression(expression: PsiParenthesizedExpression) { result = ParenthesizedExpression(converter.convertExpression(expression.getExpression())) } @@ -317,16 +315,18 @@ open class ExpressionVisitor(private val converter: Converter) : JavaElementVisi } // add qualification for static members from base classes and also this works for enum constants in switch + val context = converter.specialContext ?: expression if (target is PsiMember && target.hasModifierProperty(PsiModifier.STATIC) && target.getContainingClass() != null - && !PsiTreeUtil.isAncestor(target.getContainingClass(), expression, true) - && !isStaticallyImported(target, expression)) { + && !PsiTreeUtil.isAncestor(target.getContainingClass(), context, true) + && !target.isImported(context.getContainingFile() as PsiJavaFile)) { var member: PsiMember = target var code = Identifier.toKotlin(referenceName) - while (member.getContainingClass() != null) { - code = Identifier.toKotlin(member.getContainingClass()!!.getName()!!) + "." + code - member = member.getContainingClass()!! + while (true) { + val containingClass = member.getContainingClass() ?: break + code = Identifier.toKotlin(containingClass.getName()!!) + "." + code + member = containingClass } result = Identifier(code, false, false) return @@ -433,23 +433,4 @@ open class ExpressionVisitor(private val converter: Converter) : JavaElementVisi } return "" } - - private fun isStaticallyImported(member: PsiMember, context: PsiElement): Boolean { - val containingFile = context.getContainingFile() - val targetContainingClass = member.getContainingClass() - if (containingFile is PsiJavaFile && targetContainingClass != null) { - val importList = containingFile.getImportList(); - if (importList != null) { - return importList.getImportStaticStatements().any { importResolvesTo(it, member) } - } - } - return false - } - - private fun importResolvesTo(importStatement: PsiImportStaticStatement, member: PsiMember): Boolean { - val targetContainingClass = member.getContainingClass() - val importedClass = importStatement.resolveTargetClass() - return importedClass == targetContainingClass - && (importStatement.isOnDemand() || importStatement.getReferenceName() == member.getName()) - } } diff --git a/j2k/src/org/jetbrains/jet/j2k/visitors/TypeVisitor.kt b/j2k/src/org/jetbrains/jet/j2k/visitors/TypeVisitor.kt index c11ea5193d3..2db88fd1b88 100644 --- a/j2k/src/org/jetbrains/jet/j2k/visitors/TypeVisitor.kt +++ b/j2k/src/org/jetbrains/jet/j2k/visitors/TypeVisitor.kt @@ -19,15 +19,19 @@ package org.jetbrains.jet.j2k.visitors import com.intellij.psi.* import com.intellij.psi.impl.source.PsiClassReferenceType import org.jetbrains.jet.j2k.ast.* -import java.util.LinkedList import com.intellij.openapi.util.text.StringUtil -import java.util.ArrayList import org.jetbrains.jet.lang.resolve.java.JvmPrimitiveType import org.jetbrains.jet.j2k.TypeConverter +import java.util.ArrayList +import org.jetbrains.jet.j2k.singleOrNull2 +import org.jetbrains.jet.j2k.Converter private val PRIMITIVE_TYPES_NAMES = JvmPrimitiveType.values().map { it.getName() } -class TypeVisitor(private val converter: TypeConverter, private val importNames: Set, private val classesToImport: MutableSet) : PsiTypeVisitor() { +class TypeVisitor(private val converter: Converter) : PsiTypeVisitor() { + + private val typeConverter: TypeConverter = converter.typeConverter + override fun visitPrimitiveType(primitiveType: PsiPrimitiveType): Type { val name = primitiveType.getCanonicalText() return if (name == "void") { @@ -45,100 +49,86 @@ class TypeVisitor(private val converter: TypeConverter, private val importNames: } override fun visitArrayType(arrayType: PsiArrayType): Type { - return ArrayType(converter.convertType(arrayType.getComponentType()), Nullability.Default, converter.settings) + return ArrayType(typeConverter.convertType(arrayType.getComponentType()), Nullability.Default, converter.settings) } override fun visitClassType(classType: PsiClassType): Type { - val identifier = constructClassTypeIdentifier(classType) - val resolvedClassTypeParams = createRawTypesForResolvedReference(classType) - if (classType.getParameterCount() == 0 && resolvedClassTypeParams.size() > 0) { - val starParamList = ArrayList() - if (resolvedClassTypeParams.size() == 1) { - if ((resolvedClassTypeParams.single() as ClassType).name.name == "Any") { - starParamList.add(StarProjectionType()) - return ClassType(identifier, starParamList, Nullability.Default, converter.settings) - } - else { - return ClassType(identifier, resolvedClassTypeParams, Nullability.Default, converter.settings) - } - } - else { - return ClassType(identifier, resolvedClassTypeParams, Nullability.Default, converter.settings) - } - } - else { - return ClassType(identifier, converter.convertTypes(classType.getParameters()), Nullability.Default, converter.settings) - } + val refElement = constructReferenceElement(classType) + return ClassType(refElement, Nullability.Default, converter.settings) } - private fun constructClassTypeIdentifier(classType: PsiClassType): Identifier { + private fun constructReferenceElement(classType: PsiClassType): ReferenceElement { + val typeArgs = convertTypeArgs(classType) + val psiClass = classType.resolve() if (psiClass != null) { val javaClassName = psiClass.getQualifiedName() val kotlinClassName = toKotlinTypesMap[javaClassName] if (kotlinClassName != null) { val kotlinShortName = getShortName(kotlinClassName) - if (kotlinShortName == getShortName(javaClassName!!) && importNames.contains(getPackageName(javaClassName) + ".*")) { - classesToImport.add(kotlinClassName) + if (kotlinShortName == getShortName(javaClassName!!) && converter.importNames.contains(getPackageName(javaClassName) + ".*")) { + converter.importsToAdd?.add(kotlinClassName) } - return Identifier(kotlinShortName).assignNoPrototype() + return ReferenceElement(Identifier(kotlinShortName).assignNoPrototype(), typeArgs).assignNoPrototype() } } if (classType is PsiClassReferenceType) { - val reference = classType.getReference() - if (reference.isQualified()) { - var result = Identifier.toKotlin(reference.getReferenceName()!!) - var qualifier = reference.getQualifier() - while (qualifier != null) { - val codeRefElement = qualifier as PsiJavaCodeReferenceElement - result = Identifier.toKotlin(codeRefElement.getReferenceName()!!) + "." + result - qualifier = codeRefElement.getQualifier() - } - return Identifier(result).assignNoPrototype() - } + return converter.convertCodeReferenceElement(classType.getReference(), hasExternalQualifier = false, typeArgsConverted = typeArgs) } - return Identifier(classType.getClassName() ?: "").assignNoPrototype() + return ReferenceElement(Identifier(classType.getClassName() ?: "").assignNoPrototype(), typeArgs).assignNoPrototype() } private fun getPackageName(className: String): String = className.substring(0, className.lastIndexOf('.')) private fun getShortName(className: String): String = className.substring(className.lastIndexOf('.') + 1) + private fun convertTypeArgs(classType: PsiClassType): List { + val resolvedClassTypeParams = createRawTypesForResolvedReference(classType) + + if (classType.getParameterCount() == 0 && resolvedClassTypeParams.size() > 0) { + if ((resolvedClassTypeParams.singleOrNull2() as? ClassType)?.referenceElement?.name?.name == "Any") { + return listOf(StarProjectionType().assignNoPrototype()) + } + else { + return resolvedClassTypeParams + } + } + else { + return typeConverter.convertTypes(classType.getParameters()) + } + } + private fun createRawTypesForResolvedReference(classType: PsiClassType): List { - val typeParams = LinkedList() + val typeArgs = ArrayList() if (classType is PsiClassReferenceType) { val resolve = classType.getReference().resolve() if (resolve is PsiClass) { for (typeParam in resolve.getTypeParameters()) { val superTypes = typeParam.getSuperTypes() val boundType = if (superTypes.size > 0) { - ClassType(constructClassTypeIdentifier(superTypes[0]), - converter.convertTypes(superTypes[0].getParameters()), - Nullability.Default, - converter.settings) + ClassType(constructReferenceElement(superTypes.first()), Nullability.Default, converter.settings) } else { StarProjectionType() } - typeParams.add(boundType) + typeArgs.add(boundType) } } } - - return typeParams + return typeArgs } override fun visitWildcardType(wildcardType: PsiWildcardType): Type { return when { - wildcardType.isExtends() -> OutProjectionType(converter.convertType(wildcardType.getExtendsBound())) - wildcardType.isSuper() -> InProjectionType(converter.convertType(wildcardType.getSuperBound())) + wildcardType.isExtends() -> OutProjectionType(typeConverter.convertType(wildcardType.getExtendsBound())) + wildcardType.isSuper() -> InProjectionType(typeConverter.convertType(wildcardType.getSuperBound())) else -> StarProjectionType() } } override fun visitEllipsisType(ellipsisType: PsiEllipsisType): Type { - return VarArgType(converter.convertType(ellipsisType.getComponentType())) + return VarArgType(typeConverter.convertType(ellipsisType.getComponentType())) } class object { 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 583bce28b59..5c7acbdb325 100644 --- a/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java +++ b/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java @@ -891,6 +891,31 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv doTest("j2k/tests/testData/ast/constructors/methodCallInFactoryFun.java"); } + @TestMetadata("nestedClassNameInParameterDefaults.java") + public void testNestedClassNameInParameterDefaults() throws Exception { + doTest("j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults.java"); + } + + @TestMetadata("nestedClassNameInParameterDefaults2.java") + public void testNestedClassNameInParameterDefaults2() throws Exception { + doTest("j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults2.java"); + } + + @TestMetadata("nestedClassNameInParameterDefaults3.java") + public void testNestedClassNameInParameterDefaults3() throws Exception { + doTest("j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults3.java"); + } + + @TestMetadata("nestedClassNameInParameterDefaults4.java") + public void testNestedClassNameInParameterDefaults4() throws Exception { + doTest("j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults4.java"); + } + + @TestMetadata("nestedClassNameInSuperParameters.java") + public void testNestedClassNameInSuperParameters() throws Exception { + doTest("j2k/tests/testData/ast/constructors/nestedClassNameInSuperParameters.java"); + } + @TestMetadata("noPrimary.java") public void testNoPrimary() throws Exception { doTest("j2k/tests/testData/ast/constructors/noPrimary.java"); @@ -1732,6 +1757,16 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv doTest("j2k/tests/testData/ast/issues/kt-1074.java"); } + @TestMetadata("kt-5294.java") + public void testKt_5294() throws Exception { + doTest("j2k/tests/testData/ast/issues/kt-5294.java"); + } + + @TestMetadata("kt-5400.java") + public void testKt_5400() throws Exception { + doTest("j2k/tests/testData/ast/issues/kt-5400.java"); + } + @TestMetadata("kt-543.java") public void testKt_543() throws Exception { doTest("j2k/tests/testData/ast/issues/kt-543.java"); diff --git a/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults.java b/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults.java new file mode 100644 index 00000000000..7a3c0871328 --- /dev/null +++ b/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults.java @@ -0,0 +1,14 @@ +class A { + A(Nested nested) { + } + + A() { + this(new Nested(Nested.FIELD)); + } + + static class Nested { + Nested(int p){} + + public static final int FIELD = 0; + } +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults.kt b/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults.kt new file mode 100644 index 00000000000..2b1329fa6e2 --- /dev/null +++ b/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults.kt @@ -0,0 +1,9 @@ +class A(nested: A.Nested = A.Nested(A.Nested.FIELD)) { + + class Nested(p: Int) { + class object { + + public val FIELD: Int = 0 + } + } +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults2.java b/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults2.java new file mode 100644 index 00000000000..bc5ec29ece1 --- /dev/null +++ b/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults2.java @@ -0,0 +1,20 @@ +import A.Nested; + +class A { + A(Nested nested) { + } + + A() { + this(new Nested(Nested.FIELD)); + } + + static class Nested { + Nested(int p){} + + public static final int FIELD = 0; + } +} + +class B { + Nested nested; +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults2.kt b/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults2.kt new file mode 100644 index 00000000000..9e82b2b95da --- /dev/null +++ b/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults2.kt @@ -0,0 +1,15 @@ +import A.Nested + +class A(nested: Nested = Nested(Nested.FIELD)) { + + class Nested(p: Int) { + class object { + + public val FIELD: Int = 0 + } + } +} + +class B { + var nested: Nested +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults3.java b/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults3.java new file mode 100644 index 00000000000..7ead1b36cbd --- /dev/null +++ b/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults3.java @@ -0,0 +1,22 @@ +package pack; + +import static pack.A.Nested; + +class A { + A(Nested nested) { + } + + A() { + this(new Nested(Nested.FIELD)); + } + + static class Nested { + Nested(int p){} + + public static final int FIELD = 0; + } +} + +class B { + Nested nested; +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults3.kt b/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults3.kt new file mode 100644 index 00000000000..2cbf12aa276 --- /dev/null +++ b/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults3.kt @@ -0,0 +1,17 @@ +package pack + +import pack.A.Nested + +class A(nested: Nested = Nested(Nested.FIELD)) { + + class Nested(p: Int) { + class object { + + public val FIELD: Int = 0 + } + } +} + +class B { + var nested: Nested +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults4.java b/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults4.java new file mode 100644 index 00000000000..13c194022f6 --- /dev/null +++ b/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults4.java @@ -0,0 +1,22 @@ +package pack; + +import static pack.A.*; + +class A { + A(Nested nested) { + } + + A() { + this(new Nested(Nested.FIELD)); + } + + static class Nested { + Nested(int p){} + + public static final int FIELD = 0; + } +} + +class B { + Nested nested; +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults4.kt b/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults4.kt new file mode 100644 index 00000000000..9fadc53f799 --- /dev/null +++ b/j2k/tests/testData/ast/constructors/nestedClassNameInParameterDefaults4.kt @@ -0,0 +1,17 @@ +package pack + +import pack.A.* + +class A(nested: Nested = Nested(Nested.FIELD)) { + + class Nested(p: Int) { + class object { + + public val FIELD: Int = 0 + } + } +} + +class B { + var nested: Nested +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/constructors/nestedClassNameInSuperParameters.java b/j2k/tests/testData/ast/constructors/nestedClassNameInSuperParameters.java new file mode 100644 index 00000000000..41d02ece96f --- /dev/null +++ b/j2k/tests/testData/ast/constructors/nestedClassNameInSuperParameters.java @@ -0,0 +1,15 @@ +class Base { + Base(Nested nested){} + + static class Nested { + Nested(int p){} + + public static final int FIELD = 0; + } +} + +class Derived extends Base { + Derived() { + super(new Nested(Nested.FIELD)); + } +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/constructors/nestedClassNameInSuperParameters.kt b/j2k/tests/testData/ast/constructors/nestedClassNameInSuperParameters.kt new file mode 100644 index 00000000000..00bb71e559e --- /dev/null +++ b/j2k/tests/testData/ast/constructors/nestedClassNameInSuperParameters.kt @@ -0,0 +1,11 @@ +class Base(nested: Base.Nested) { + + class Nested(p: Int) { + class object { + + public val FIELD: Int = 0 + } + } +} + +class Derived : Base(Base.Nested(Base.Nested.FIELD)) \ No newline at end of file diff --git a/j2k/tests/testData/ast/issues/kt-5294.java b/j2k/tests/testData/ast/issues/kt-5294.java new file mode 100644 index 00000000000..cdb89eaf4c3 --- /dev/null +++ b/j2k/tests/testData/ast/issues/kt-5294.java @@ -0,0 +1,11 @@ +import java.util.List; + +class X { + private final List list; + + X(List list) { + this.list = list; + } + + class Y{} +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/issues/kt-5294.kt b/j2k/tests/testData/ast/issues/kt-5294.kt new file mode 100644 index 00000000000..afbba72669b --- /dev/null +++ b/j2k/tests/testData/ast/issues/kt-5294.kt @@ -0,0 +1,4 @@ +class X(private val list: List) { + + inner class Y +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/issues/kt-5400.java b/j2k/tests/testData/ast/issues/kt-5400.java new file mode 100644 index 00000000000..c4c379a5f16 --- /dev/null +++ b/j2k/tests/testData/ast/issues/kt-5400.java @@ -0,0 +1,7 @@ +class Base { + class Nested{} +} + +class Derived extends Base { + Nested field; +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/issues/kt-5400.kt b/j2k/tests/testData/ast/issues/kt-5400.kt new file mode 100644 index 00000000000..ca4e3303112 --- /dev/null +++ b/j2k/tests/testData/ast/issues/kt-5400.kt @@ -0,0 +1,7 @@ +class Base { + inner class Nested +} + +class Derived : Base() { + var field: Base.Nested +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/toKotlinClasses/TypeParameterBound.kt b/j2k/tests/testData/ast/toKotlinClasses/TypeParameterBound.kt index 89db7b8c731..15cd57f1d3c 100644 --- a/j2k/tests/testData/ast/toKotlinClasses/TypeParameterBound.kt +++ b/j2k/tests/testData/ast/toKotlinClasses/TypeParameterBound.kt @@ -1,6 +1,6 @@ import java.util.* -import kotlin.List import kotlin.Iterator +import kotlin.List trait I>>