diff --git a/j2k/src/org/jetbrains/jet/j2k/ConstructorConverter.kt b/j2k/src/org/jetbrains/jet/j2k/ConstructorConverter.kt new file mode 100644 index 00000000000..a189c22d738 --- /dev/null +++ b/j2k/src/org/jetbrains/jet/j2k/ConstructorConverter.kt @@ -0,0 +1,250 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.j2k + +import com.intellij.psi.* +import org.jetbrains.jet.j2k.ast.* +import org.jetbrains.jet.j2k.visitors.ExpressionVisitor +import com.intellij.psi.util.PsiUtil +import java.util.HashMap +import java.util.ArrayList +import java.util.HashSet + +class ConstructorConverter(private val converter: Converter) { + private val typeConverter = converter.typeConverter + + public fun convertConstructor(constructor: PsiMethod, + annotations: Annotations, + modifiers: Modifiers, + membersToRemove: MutableSet, + postProcessBody: (Block) -> Block): Member { + if (constructor.isPrimaryConstructor()) { + return convertPrimaryConstructor(constructor, annotations, modifiers, membersToRemove, postProcessBody) + } + else { + val params = converter.convertParameterList(constructor.getParameterList()) + var body = postProcessBody(converter.convertBlock(constructor.getBody())) + val containingClass = constructor.getContainingClass() + val typeParameterList = converter.convertTypeParameterList(containingClass?.getTypeParameterList()) + val factoryFunctionType = ClassType(containingClass?.declarationIdentifier() ?: Identifier.Empty, + typeParameterList.parameters, + Nullability.NotNull, + converter.settings).assignNoPrototype() + return FactoryFunction(annotations, modifiers, factoryFunctionType, params, typeParameterList, body) + } + } + + private fun convertPrimaryConstructor(constructor: PsiMethod, + annotations: Annotations, + modifiers: Modifiers, + membersToRemove: MutableSet, + postProcessBody: (Block) -> Block): PrimaryConstructor { + val params = constructor.getParameterList().getParameters() + val parameterToField = HashMap>() + val body = constructor.getBody() + val block = if (body != null) { + val statementsToRemove = HashSet() + val usageReplacementMap = HashMap() + for (parameter in params) { + val (field, initializationStatement) = findBackingFieldForConstructorParameter(parameter, constructor) ?: continue + + val fieldType = typeConverter.convertVariableType(field) + val parameterType = typeConverter.convertVariableType(parameter) + // types can be different only in nullability + val `type` = if (fieldType == parameterType) { + fieldType + } + else if (fieldType.toNotNullType() == parameterType.toNotNullType()) { + if (fieldType.isNullable) fieldType else parameterType // prefer nullable one + } + else { + continue + } + + parameterToField.put(parameter, field to `type`) + statementsToRemove.add(initializationStatement) + membersToRemove.add(field) + + if (field.getName() != parameter.getName()) { + usageReplacementMap.put(parameter, field.getName()!!) + } + } + + val correctedConverter = converter.withExpressionVisitor { ExpressionVisitor(it, usageReplacementMap) } + postProcessBody(correctedConverter.convertBlock(body, false, { !statementsToRemove.contains(it) })) + } + else { + Block.Empty + } + + val parameterList = ParameterList(params.map { parameter -> + if (!parameterToField.containsKey(parameter)) { + converter.convertParameter(parameter) + } + else { + val (field, `type`) = parameterToField[parameter]!! + Parameter(field.declarationIdentifier(), + `type`, + if (converter.isVal(field)) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var, + converter.convertAnnotations(parameter) + converter.convertAnnotations(field), + converter.convertModifiers(field).filter { it in ACCESS_MODIFIERS }).assignPrototypes(listOf(parameter, field), CommentsAndSpacesInheritance(blankLinesBefore = false)) + } + }).assignPrototype(constructor.getParameterList()) + return PrimaryConstructor(annotations, modifiers, parameterList, block).assignPrototype(constructor) + } + + private fun findBackingFieldForConstructorParameter(parameter: PsiParameter, constructor: PsiMethod): Pair? { + val body = constructor.getBody() ?: return null + + val refs = findVariableUsages(parameter, body) + + if (refs.any { PsiUtil.isAccessedForWriting(it) }) return null + + for (ref in refs) { + val assignment = ref.getParent() as? PsiAssignmentExpression ?: continue + if (assignment.getOperationSign().getTokenType() != JavaTokenType.EQ) continue + val assignee = assignment.getLExpression() as? PsiReferenceExpression ?: continue + if (!isQualifierEmptyOrThis(assignee)) continue + val field = assignee.resolve() as? PsiField ?: continue + if (field.getContainingClass() != constructor.getContainingClass()) continue + if (field.hasModifierProperty(PsiModifier.STATIC)) continue + if (field.getInitializer() != null) continue + + // assignment should be a top-level statement + val statement = assignment.getParent() as? PsiExpressionStatement ?: continue + if (statement.getParent() != body) continue + + // and no other assignments to field should exist in the constructor + if (findVariableUsages(field, body).any { it != assignee && PsiUtil.isAccessedForWriting(it) && isQualifierEmptyOrThis(it) }) continue + //TODO: check access to field before assignment + + return field to statement + } + + return null + } + + public fun postProcessConstructors(classBody: ClassBody, psiClass: PsiClass): ClassBody { + if (psiClass.getPrimaryConstructor() == null && psiClass.getConstructors().size > 1) { + return generateArtificialPrimaryConstructor(psiClass.declarationIdentifier(), classBody) + } + else { + correctFactoryFunctions(classBody, psiClass.getName()!!) + return classBody + } + } + + private fun generateArtificialPrimaryConstructor(className: Identifier, classBody: ClassBody): ClassBody { + assert(classBody.primaryConstructorSignature == null) + + val finalOrWithEmptyInitializerFields = classBody.members.filterIsInstance(javaClass()).filter { it.isVal || it.initializer.isEmpty } + val initializers = HashMap() + for (factoryFunction in classBody.factoryFunctions()) { + for (field in finalOrWithEmptyInitializerFields) { + initializers.put(field, getDefaultInitializer(field)) + } + + val statements = ArrayList() + for (statement in factoryFunction.body!!.statements) { + var keepStatement = true + if (statement is AssignmentExpression) { + val assignee = statement.left + if (assignee is QualifiedExpression && (assignee.qualifier as? Identifier)?.name == FactoryFunction.tempValName) { + val name = (assignee.identifier as Identifier).name + for (field in finalOrWithEmptyInitializerFields) { + if (name == field.identifier.name) { + initializers.put(field, statement.right) + keepStatement = false + } + + } + } + + } + + if (keepStatement) { + statements.add(statement) + } + } + + val initializer = MethodCallExpression.buildNotNull(null, + className.name, + finalOrWithEmptyInitializerFields.map { initializers[it]!! }).assignNoPrototype() + if (statements.isNotEmpty()) { + val localVar = LocalVariable(FactoryFunction.tempValIdentifier(), + Annotations.Empty, + Modifiers.Empty, + null, + initializer, + true).assignNoPrototype() + statements.add(0, DeclarationStatement(listOf(localVar)).assignNoPrototype()) + statements.add(ReturnStatement(FactoryFunction.tempValIdentifier()).assignNoPrototype()) + } + else { + statements.add(ReturnStatement(initializer).assignNoPrototype()) + } + + factoryFunction.body = Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype() + } + + val parameters = finalOrWithEmptyInitializerFields.map { field -> + val varValModifier = if (field.isVal) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var + Parameter(field.identifier, field.`type`, varValModifier, field.annotations, field.modifiers.filter { it in ACCESS_MODIFIERS }).assignPrototypesFrom(field) + } + + val modifiers = Modifiers(listOf(Modifier.PRIVATE)).assignNoPrototype() + val parameterList = ParameterList(parameters).assignNoPrototype() + val constructorSignature = PrimaryConstructorSignature(modifiers, parameterList).assignNoPrototype() + val updatedMembers = classBody.members.filter { !finalOrWithEmptyInitializerFields.contains(it) } + return ClassBody(constructorSignature, updatedMembers, classBody.classObjectMembers, classBody.lBrace, classBody.rBrace) + } + + private fun correctFactoryFunctions(classBody: ClassBody, className: String) { + for (factoryFunction in classBody.factoryFunctions()) { + val body = factoryFunction.body!! + val statements = correctFactoryFunctionStatements(body, className) + factoryFunction.body = Block(statements, body.lBrace, body.rBrace).assignPrototypesFrom(body) + } + } + + private fun correctFactoryFunctionStatements(body: Block, className: String): List { + val statements = ArrayList(body.statements) + + // searching for other constructor call in form "this(...)" + // it's not necessary the first statement because of statements inserted for writable parameters + for (i in statements.indices) { + val statement = statements[i] + if (statement is MethodCallExpression) { + if ((statement.methodExpression as? Identifier)?.name == "this") { + val constructorCall = MethodCallExpression.buildNotNull(null, className, statement.arguments).assignPrototypesFrom(statement) + if (i == statements.lastIndex) { // constructor call is the last statement - no intermediate variable needed + statements[i] = ReturnStatement(constructorCall).assignNoPrototype() + return statements + } + val localVar = LocalVariable(FactoryFunction.tempValIdentifier(), Annotations.Empty, Modifiers.Empty, null, constructorCall, true).assignNoPrototype() + statements[i] = DeclarationStatement(listOf(localVar)).assignNoPrototype() + break + } + } + } + + statements.add(ReturnStatement(FactoryFunction.tempValIdentifier()).assignNoPrototype()) + return statements + } + + private fun ClassBody.factoryFunctions() = classObjectMembers.filterIsInstance(javaClass()) +} \ No newline at end of file diff --git a/j2k/src/org/jetbrains/jet/j2k/Converter.kt b/j2k/src/org/jetbrains/jet/j2k/Converter.kt index c61df36b51c..856d9dcbc18 100644 --- a/j2k/src/org/jetbrains/jet/j2k/Converter.kt +++ b/j2k/src/org/jetbrains/jet/j2k/Converter.kt @@ -38,10 +38,10 @@ public class Converter private(val project: Project, val settings: ConverterSett val methodReturnType: PsiType?, val expressionVisitorFactory: (Converter) -> ExpressionVisitor, val statementVisitorFactory: (Converter) -> StatementVisitor) + val typeConverter: TypeConverter = state.typeConverter + val methodReturnType: PsiType? = state.methodReturnType - val typeConverter: TypeConverter get() = state.typeConverter - val methodReturnType: PsiType? get() = state.methodReturnType - + private val constructorConverter = ConstructorConverter(this) private val expressionVisitor = state.expressionVisitorFactory(this) private val statementVisitor = state.statementVisitorFactory(this) @@ -187,12 +187,7 @@ public class Converter private(val project: Project, val settings: ConverterSett psiClass.isEnum() -> Enum(name, annotations, modifiers, typeParameters, listOf(), listOf(), implementsTypes, classBody) else -> { - if (psiClass.getPrimaryConstructor() == null && psiClass.getConstructors().size > 1) { - classBody = generateArtificialPrimaryConstructor(name, classBody) - } - else { - correctFactoryFunctions(classBody, psiClass.getName()!!) - } + classBody = constructorConverter.postProcessConstructors(classBody, psiClass) val baseClassParams: List = run { val superVisitor = SuperVisitor() @@ -219,106 +214,6 @@ public class Converter private(val project: Project, val settings: ConverterSett }.assignPrototype(psiClass) } - private fun ClassBody.factoryFunctions() = classObjectMembers.filterIsInstance(javaClass()) - - private fun generateArtificialPrimaryConstructor(className: Identifier, classBody: ClassBody): ClassBody { - assert(classBody.primaryConstructorSignature == null) - - val finalOrWithEmptyInitializerFields = classBody.members.filterIsInstance(javaClass()).filter { it.isVal || it.initializer.isEmpty } - val initializers = HashMap() - for (factoryFunction in classBody.factoryFunctions()) { - for (field in finalOrWithEmptyInitializerFields) { - initializers.put(field, getDefaultInitializer(field)) - } - - val statements = ArrayList() - for (statement in factoryFunction.body!!.statements) { - var keepStatement = true - if (statement is AssignmentExpression) { - val assignee = statement.left - if (assignee is QualifiedExpression && (assignee.qualifier as? Identifier)?.name == FactoryFunction.tempValName) { - val name = (assignee.identifier as Identifier).name - for (field in finalOrWithEmptyInitializerFields) { - if (name == field.identifier.name) { - initializers.put(field, statement.right) - keepStatement = false - } - - } - } - - } - - if (keepStatement) { - statements.add(statement) - } - } - - val initializer = MethodCallExpression.buildNotNull(null, - className.name, - finalOrWithEmptyInitializerFields.map { initializers[it]!! }).assignNoPrototype() - if (statements.isNotEmpty()) { - val localVar = LocalVariable(FactoryFunction.tempValIdentifier(), - Annotations.Empty, - Modifiers.Empty, - null, - initializer, - true).assignNoPrototype() - statements.add(0, DeclarationStatement(listOf(localVar)).assignNoPrototype()) - statements.add(ReturnStatement(FactoryFunction.tempValIdentifier()).assignNoPrototype()) - } - else { - statements.add(ReturnStatement(initializer).assignNoPrototype()) - } - - factoryFunction.body = Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype() - } - - val parameters = finalOrWithEmptyInitializerFields.map { field -> - val varValModifier = if (field.isVal) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var - Parameter(field.identifier, field.`type`, varValModifier, field.annotations, field.modifiers.filter { it in ACCESS_MODIFIERS }).assignPrototypesFrom(field) - } - - val modifiers = Modifiers(listOf(Modifier.PRIVATE)).assignNoPrototype() - val parameterList = ParameterList(parameters).assignNoPrototype() - val constructorSignature = PrimaryConstructorSignature(modifiers, parameterList).assignNoPrototype() - val updatedMembers = classBody.members.filter { !finalOrWithEmptyInitializerFields.contains(it) } - return ClassBody(constructorSignature, updatedMembers, classBody.classObjectMembers, classBody.lBrace, classBody.rBrace) - } - - private fun correctFactoryFunctions(classBody: ClassBody, className: String) { - for (factoryFunction in classBody.factoryFunctions()) { - val body = factoryFunction.body!! - val statements = correctFactoryFunctionStatements(body, className) - factoryFunction.body = Block(statements, body.lBrace, body.rBrace).assignPrototypesFrom(body) - } - } - - private fun correctFactoryFunctionStatements(body: Block, className: String): List { - val statements = ArrayList(body.statements) - - // searching for other constructor call in form "this(...)" - // it's not necessary the first statement because of statements inserted for writable parameters - for (i in statements.indices) { - val statement = statements[i] - if (statement is MethodCallExpression) { - if ((statement.methodExpression as? Identifier)?.name == "this") { - val constructorCall = MethodCallExpression.buildNotNull(null, className, statement.arguments).assignPrototypesFrom(statement) - if (i == statements.lastIndex) { // constructor call is the last statement - no intermediate variable needed - statements[i] = ReturnStatement(constructorCall).assignNoPrototype() - return statements - } - val localVar = LocalVariable(FactoryFunction.tempValIdentifier(), Annotations.Empty, Modifiers.Empty, null, constructorCall, true).assignNoPrototype() - statements[i] = DeclarationStatement(listOf(localVar)).assignNoPrototype() - break - } - } - } - - statements.add(ReturnStatement(FactoryFunction.tempValIdentifier()).assignNoPrototype()) - return statements - } - private fun convertInitializer(initializer: PsiClassInitializer): Initializer { return Initializer(convertBlock(initializer.getBody()), convertModifiers(initializer)).assignPrototype(initializer) } @@ -368,7 +263,7 @@ public class Converter private(val project: Project, val settings: ConverterSett return if (convertedType == initializerType) null else convertedType } - private fun isVal(field: PsiField): Boolean { + fun isVal(field: PsiField): Boolean { if (field.hasModifierProperty(PsiModifier.FINAL)) return true if (!field.hasModifierProperty(PsiModifier.PRIVATE)) return false val containingClass = field.getContainingClass() ?: return false @@ -421,20 +316,7 @@ public class Converter private(val project: Project, val settings: ConverterSett } if (method.isConstructor()) { - if (method.isPrimaryConstructor()) { - return convertPrimaryConstructor(method, annotations, modifiers, membersToRemove, postProcessBody) - } - else { - val params = convertParameterList(method.getParameterList()) - var body = postProcessBody(convertBlock(method.getBody())) - val containingClass = method.getContainingClass() - val typeParameterList = convertTypeParameterList(containingClass?.getTypeParameterList()) - val factoryFunctionType = ClassType(containingClass?.declarationIdentifier() ?: Identifier.Empty, - typeParameterList.parameters, - Nullability.NotNull, - settings).assignNoPrototype() - return FactoryFunction(annotations, modifiers, factoryFunctionType, params, typeParameterList, body) - } + return constructorConverter.convertConstructor(method, annotations, modifiers, membersToRemove, postProcessBody) } else { val isOverride = isOverride(method) @@ -494,96 +376,6 @@ public class Converter private(val project: Project, val settings: ConverterSett return false } - private fun convertPrimaryConstructor(constructor: PsiMethod, - annotations: Annotations, - modifiers: Modifiers, - membersToRemove: MutableSet, - postProcessBody: (Block) -> Block): PrimaryConstructor { - val params = constructor.getParameterList().getParameters() - val parameterToField = HashMap>() - val body = constructor.getBody() - val block = if (body != null) { - val statementsToRemove = HashSet() - val usageReplacementMap = HashMap() - for (parameter in params) { - val (field, initializationStatement) = findBackingFieldForConstructorParameter(parameter, constructor) ?: continue - - val fieldType = typeConverter.convertVariableType(field) - val parameterType = typeConverter.convertVariableType(parameter) - // types can be different only in nullability - val `type` = if (fieldType == parameterType) { - fieldType - } - else if (fieldType.toNotNullType() == parameterType.toNotNullType()) { - if (fieldType.isNullable) fieldType else parameterType // prefer nullable one - } - else { - continue - } - - parameterToField.put(parameter, field to `type`) - statementsToRemove.add(initializationStatement) - membersToRemove.add(field) - - if (field.getName() != parameter.getName()) { - usageReplacementMap.put(parameter, field.getName()!!) - } - } - - val correctedConverter = withExpressionVisitor { ExpressionVisitor(it, usageReplacementMap) } - postProcessBody(correctedConverter.convertBlock(body, false, { !statementsToRemove.contains(it) })) - } - else { - Block.Empty - } - - val parameterList = ParameterList(params.map { parameter -> - if (!parameterToField.containsKey(parameter)) { - convertParameter(parameter) - } - else { - val (field, `type`) = parameterToField[parameter]!! - Parameter(field.declarationIdentifier(), - `type`, - if (isVal(field)) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var, - convertAnnotations(parameter) + convertAnnotations(field), - convertModifiers(field).filter { it in ACCESS_MODIFIERS }).assignPrototypes(listOf(parameter, field), CommentsAndSpacesInheritance(blankLinesBefore = false)) - } - }).assignPrototype(constructor.getParameterList()) - return PrimaryConstructor(annotations, modifiers, parameterList, block).assignPrototype(constructor) - } - - private fun findBackingFieldForConstructorParameter(parameter: PsiParameter, constructor: PsiMethod): Pair? { - val body = constructor.getBody() ?: return null - - val refs = findVariableUsages(parameter, body) - - if (refs.any { PsiUtil.isAccessedForWriting(it) }) return null - - for (ref in refs) { - val assignment = ref.getParent() as? PsiAssignmentExpression ?: continue - if (assignment.getOperationSign().getTokenType() != JavaTokenType.EQ) continue - val assignee = assignment.getLExpression() as? PsiReferenceExpression ?: continue - if (!isQualifierEmptyOrThis(assignee)) continue - val field = assignee.resolve() as? PsiField ?: continue - if (field.getContainingClass() != constructor.getContainingClass()) continue - if (field.hasModifierProperty(PsiModifier.STATIC)) continue - if (field.getInitializer() != null) continue - - // assignment should be a top-level statement - val statement = assignment.getParent() as? PsiExpressionStatement ?: continue - if (statement.getParent() != body) continue - - // and no other assignments to field should exist in the constructor - if (findVariableUsages(field, body).any { it != assignee && PsiUtil.isAccessedForWriting(it) && isQualifierEmptyOrThis(it) }) continue - //TODO: check access to field before assignment - - return field to statement - } - - return null - } - fun convertBlock(block: PsiCodeBlock?, notEmpty: Boolean = true, statementFilter: (PsiStatement) -> Boolean = { true }): Block { if (block == null) return Block.Empty