diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/commonUtils.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/commonUtils.kt new file mode 100644 index 00000000000..24ea15bedc0 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/commonUtils.kt @@ -0,0 +1,145 @@ +/* + * Copyright 2010-2016 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.kotlin.idea.intentions.loopToCallChain + +import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.search.LocalSearchScope +import com.intellij.psi.search.searches.ReferencesSearch +import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.references.KtSimpleNameReference +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.idea.references.readWriteAccess +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector +import org.jetbrains.kotlin.psi.psiUtil.parents +import org.jetbrains.kotlin.psi.psiUtil.siblings +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull + +fun KtExpression?.isTrueConstant() + = this != null && node?.elementType == KtNodeTypes.BOOLEAN_CONSTANT && text == "true" + +fun KtExpression?.isFalseConstant() + = this != null && node?.elementType == KtNodeTypes.BOOLEAN_CONSTANT && text == "false" + +fun KtExpression?.isVariableReference(variable: KtCallableDeclaration): Boolean { + return this is KtNameReferenceExpression && this.mainReference.isReferenceTo(variable) +} + +fun KtExpression?.isSimpleName(name: Name): Boolean { + return this is KtNameReferenceExpression && this.getQualifiedExpressionForSelector() == null && this.getReferencedNameAsName() == name +} + +fun KtCallableDeclaration.hasUsages(inElement: KtElement): Boolean { + return hasUsages(listOf(inElement)) +} + +fun KtCallableDeclaration.hasUsages(inElements: Collection): Boolean { + assert(this.isPhysical) + // TODO: it's a temporary workaround about strange dead-lock when running inspections + return inElements.any { ReferencesSearch.search(this, LocalSearchScope(it)).any() } +// return ReferencesSearch.search(this, LocalSearchScope(inElements.toTypedArray())).any() +} + +fun KtProperty.hasWriteUsages(): Boolean { + assert(this.isPhysical) + if (!isVar) return false + return ReferencesSearch.search(this, useScope).any { + (it as? KtSimpleNameReference)?.element?.readWriteAccess(useResolveForReadWrite = true)?.isWrite == true + } +} + +fun KtProperty.countUsages(inElement: KtElement): Int { + assert(this.isPhysical) + return ReferencesSearch.search(this, LocalSearchScope(inElement)).count() +} + +fun KtProperty.countUsages(inElements: Collection): Int { + assert(this.isPhysical) + // TODO: it's a temporary workaround about strange dead-lock when running inspections + return inElements.sumBy { ReferencesSearch.search(this, LocalSearchScope(it)).count() } +} + +fun KtProperty.countUsages(): Int { + assert(this.isPhysical) + return ReferencesSearch.search(this, useScope).count() +} + +fun KtProperty.countWriteUsages(inElement: KtElement): Int { + assert(this.isPhysical) + if (!isVar) return 0 + return ReferencesSearch.search(this, LocalSearchScope(inElement)).count { + (it as? KtSimpleNameReference)?.element?.readWriteAccess(useResolveForReadWrite = true)?.isWrite == true + } +} + +fun KtExpressionWithLabel.targetLoop(): KtLoopExpression? { + val label = getTargetLabel() + if (label == null) { + return parents.firstIsInstance() + } + else { + //TODO: does PARTIAL always work here? + return analyze(BodyResolveMode.PARTIAL)[BindingContext.LABEL_TARGET, label] as? KtLoopExpression + } +} + +fun KtExpression.isPlusPlusOf(): KtExpression? { + if (this !is KtUnaryExpression) return null + if (operationToken != KtTokens.PLUSPLUS) return null + return baseExpression +} + +fun KtExpression.previousStatement(): KtExpression? { + val statement = unwrapIfLabeled() + if (statement.parent !is KtBlockExpression) return null + return statement.siblings(forward = false, withItself = false).firstIsInstanceOrNull() +} + +fun KtExpression.nextStatement(): KtExpression? { + val statement = unwrapIfLabeled() + if (statement.parent !is KtBlockExpression) return null + return statement.siblings(forward = true, withItself = false).firstIsInstanceOrNull() +} + +fun KtExpression.unwrapIfLabeled(): KtExpression { + var statement = this + while (true) { + statement = statement.parent as? KtLabeledExpression ?: return statement + } +} + +fun KtLoopExpression.deleteWithLabels() { + unwrapIfLabeled().delete() +} + +fun PsiChildRange.withoutFirstStatement(): PsiChildRange { + val newFirst = first!!.siblings(forward = true, withItself = false).first { it !is PsiWhiteSpace } + return PsiChildRange(newFirst, last) +} + +fun PsiChildRange.withoutLastStatement(): PsiChildRange { + val newLast = last!!.siblings(forward = false, withItself = false).first { it !is PsiWhiteSpace } + return PsiChildRange(first, newLast) +} + diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/findOperationGeneration.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/findOperationGeneration.kt new file mode 100644 index 00000000000..c9e0d6bcbdd --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/findOperationGeneration.kt @@ -0,0 +1,151 @@ +/* + * Copyright 2010-2016 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.kotlin.idea.intentions.loopToCallChain + +import org.jetbrains.kotlin.descriptors.VariableDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor +import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.types.typeUtil.TypeNullability +import org.jetbrains.kotlin.types.typeUtil.nullability + +interface FindOperatorGenerator { + val functionName: String + + val shouldUseInputVariable: Boolean + + fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression + + val chainCallCount: Int + get() = 1 +} + +fun buildFindOperationGenerator( + valueIfFound: KtExpression, + valueIfNotFound: KtExpression, + inputVariable: KtCallableDeclaration, + findFirst: Boolean +): FindOperatorGenerator? { + assert(valueIfFound.isPhysical) + assert(valueIfNotFound.isPhysical) + + fun generateChainedCall(stdlibFunName: String, chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression { + return if (filter == null) { + chainedCallGenerator.generate("$stdlibFunName()") + } + else { + val lambda = generateLambda(inputVariable, filter) + chainedCallGenerator.generate("$stdlibFunName $0:'{}'", lambda) + } + } + + class SimpleGenerator(override val functionName: String, override val shouldUseInputVariable: Boolean) : FindOperatorGenerator { + override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression { + return generateChainedCall(functionName, chainedCallGenerator, filter) + } + } + + val inputVariableCanHoldNull = (inputVariable.resolveToDescriptor() as VariableDescriptor).type.nullability() != TypeNullability.NOT_NULL + + fun FindOperatorGenerator.useElvisOperatorIfNeeded(): FindOperatorGenerator? { + if (valueIfNotFound.isNullExpression()) return this + + // we cannot use ?: if found value can be null + if (inputVariableCanHoldNull) return null + + return object: FindOperatorGenerator by this { + override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression { + val generated = this@useElvisOperatorIfNeeded.generate(chainedCallGenerator, filter) + return KtPsiFactory(generated).createExpressionByPattern("$0 ?: $1", generated, valueIfNotFound) + } + } + } + + when { + valueIfFound.isVariableReference(inputVariable) -> { + val generator = SimpleGenerator(if (findFirst) "firstOrNull" else "lastOrNull", shouldUseInputVariable = true) + return generator.useElvisOperatorIfNeeded() + } + + valueIfFound.isTrueConstant() && valueIfNotFound.isFalseConstant() -> return SimpleGenerator("any", shouldUseInputVariable = false) + + valueIfFound.isFalseConstant() && valueIfNotFound.isTrueConstant() -> return SimpleGenerator("none", shouldUseInputVariable = false) + + inputVariable.hasUsages(valueIfFound) -> { + if (!findFirst) return null // too dangerous because of side effects + + // specially handle the case when the result expression is "." or "?." + val qualifiedExpression = valueIfFound as? KtQualifiedExpression + if (qualifiedExpression != null) { + val receiver = qualifiedExpression.receiverExpression + val selector = qualifiedExpression.selectorExpression + if (receiver.isVariableReference(inputVariable) && selector != null && !inputVariable.hasUsages(selector)) { + return object: FindOperatorGenerator { + override val functionName: String + get() = "firstOrNull" + + override val shouldUseInputVariable: Boolean + get() = true + + override val chainCallCount: Int + get() = 2 + + override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression { + val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, filter) + return chainedCallGenerator.generate("$0", selector, receiver = findFirstCall, safeCall = true) + } + }.useElvisOperatorIfNeeded() + } + } + + // in case of nullable input variable we cannot distinguish by the result of "firstOrNull" whether nothing was found or 'null' was found + if (inputVariableCanHoldNull) return null + + return object: FindOperatorGenerator { + override val functionName: String + get() = "firstOrNull" + + override val shouldUseInputVariable: Boolean + get() = true + + override val chainCallCount: Int + get() = 2 // also includes "let" + + override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression { + val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, filter) + val letBody = generateLambda(inputVariable, valueIfFound) + return chainedCallGenerator.generate("let $0:'{}'", letBody, receiver = findFirstCall, safeCall = true) + } + }.useElvisOperatorIfNeeded() + } + + else -> { + return object: FindOperatorGenerator { + override val functionName: String + get() = "any" + + override val shouldUseInputVariable: Boolean + get() = false + + override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression { + val chainedCall = generateChainedCall(functionName, chainedCallGenerator, filter) + return KtPsiFactory(chainedCall).createExpressionByPattern("if ($0) $1 else $2", chainedCall, valueIfFound, valueIfNotFound) + } + } + } + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt index b1459adcb80..d5dfdd8ffb1 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt @@ -18,27 +18,16 @@ package org.jetbrains.kotlin.idea.intentions.loopToCallChain import com.intellij.openapi.util.Key import com.intellij.openapi.util.UserDataHolderBase -import com.intellij.psi.PsiWhiteSpace -import com.intellij.psi.search.LocalSearchScope -import com.intellij.psi.search.searches.ReferencesSearch -import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.descriptors.ConstructorDescriptor -import org.jetbrains.kotlin.descriptors.VariableDescriptor import org.jetbrains.kotlin.diagnostics.Severity import org.jetbrains.kotlin.idea.analysis.analyzeInContext import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade -import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.core.copied import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.imports.importableFqName -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression -import org.jetbrains.kotlin.idea.references.KtSimpleNameReference import org.jetbrains.kotlin.idea.references.mainReference -import org.jetbrains.kotlin.idea.references.readWriteAccess import org.jetbrains.kotlin.idea.util.getResolutionScope -import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.resolve.BindingContext @@ -47,10 +36,6 @@ import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode -import org.jetbrains.kotlin.types.typeUtil.TypeNullability -import org.jetbrains.kotlin.types.typeUtil.nullability -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull fun generateLambda(inputVariable: KtCallableDeclaration, expression: KtExpression): KtLambdaExpression { val psiFactory = KtPsiFactory(expression) @@ -101,241 +86,6 @@ fun generateLambda(expression: KtExpression, vararg inputVariables: KtCallableDe } as KtLambdaExpression } -fun KtExpression?.isTrueConstant() - = this != null && node?.elementType == KtNodeTypes.BOOLEAN_CONSTANT && text == "true" - -fun KtExpression?.isFalseConstant() - = this != null && node?.elementType == KtNodeTypes.BOOLEAN_CONSTANT && text == "false" - -fun KtExpression?.isVariableReference(variable: KtCallableDeclaration): Boolean { - return this is KtNameReferenceExpression && this.mainReference.isReferenceTo(variable) -} - -fun KtExpression?.isSimpleName(name: Name): Boolean { - return this is KtNameReferenceExpression && this.getQualifiedExpressionForSelector() == null && this.getReferencedNameAsName() == name -} - -fun KtCallableDeclaration.hasUsages(inElement: KtElement): Boolean { - return hasUsages(listOf(inElement)) -} - -fun KtCallableDeclaration.hasUsages(inElements: Collection): Boolean { - assert(this.isPhysical) - // TODO: it's a temporary workaround about strange dead-lock when running inspections - return inElements.any { ReferencesSearch.search(this, LocalSearchScope(it)).any() } -// return ReferencesSearch.search(this, LocalSearchScope(inElements.toTypedArray())).any() -} - -fun KtProperty.hasWriteUsages(): Boolean { - assert(this.isPhysical) - if (!isVar) return false - return ReferencesSearch.search(this, useScope).any { - (it as? KtSimpleNameReference)?.element?.readWriteAccess(useResolveForReadWrite = true)?.isWrite == true - } -} - -fun KtProperty.countUsages(inElement: KtElement): Int { - assert(this.isPhysical) - return ReferencesSearch.search(this, LocalSearchScope(inElement)).count() -} - -fun KtProperty.countUsages(inElements: Collection): Int { - assert(this.isPhysical) - // TODO: it's a temporary workaround about strange dead-lock when running inspections - return inElements.sumBy { ReferencesSearch.search(this, LocalSearchScope(it)).count() } -} - -fun KtProperty.countUsages(): Int { - assert(this.isPhysical) - return ReferencesSearch.search(this, useScope).count() -} - -fun KtProperty.countWriteUsages(inElement: KtElement): Int { - assert(this.isPhysical) - if (!isVar) return 0 - return ReferencesSearch.search(this, LocalSearchScope(inElement)).count { - (it as? KtSimpleNameReference)?.element?.readWriteAccess(useResolveForReadWrite = true)?.isWrite == true - } -} - - -interface FindOperatorGenerator { - val functionName: String - - val shouldUseInputVariable: Boolean - - fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression - - val chainCallCount: Int - get() = 1 -} - -fun buildFindOperationGenerator( - valueIfFound: KtExpression, - valueIfNotFound: KtExpression, - inputVariable: KtCallableDeclaration, - findFirst: Boolean -): FindOperatorGenerator? { - assert(valueIfFound.isPhysical) - assert(valueIfNotFound.isPhysical) - - fun generateChainedCall(stdlibFunName: String, chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression { - return if (filter == null) { - chainedCallGenerator.generate("$stdlibFunName()") - } - else { - val lambda = generateLambda(inputVariable, filter) - chainedCallGenerator.generate("$stdlibFunName $0:'{}'", lambda) - } - } - - class SimpleGenerator(override val functionName: String, override val shouldUseInputVariable: Boolean) : FindOperatorGenerator { - override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression { - return generateChainedCall(functionName, chainedCallGenerator, filter) - } - } - - val inputVariableCanHoldNull = (inputVariable.resolveToDescriptor() as VariableDescriptor).type.nullability() != TypeNullability.NOT_NULL - - fun FindOperatorGenerator.useElvisOperatorIfNeeded(): FindOperatorGenerator? { - if (valueIfNotFound.isNullExpression()) return this - - // we cannot use ?: if found value can be null - if (inputVariableCanHoldNull) return null - - return object: FindOperatorGenerator by this { - override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression { - val generated = this@useElvisOperatorIfNeeded.generate(chainedCallGenerator, filter) - return KtPsiFactory(generated).createExpressionByPattern("$0 ?: $1", generated, valueIfNotFound) - } - } - } - - when { - valueIfFound.isVariableReference(inputVariable) -> { - val generator = SimpleGenerator(if (findFirst) "firstOrNull" else "lastOrNull", shouldUseInputVariable = true) - return generator.useElvisOperatorIfNeeded() - } - - valueIfFound.isTrueConstant() && valueIfNotFound.isFalseConstant() -> return SimpleGenerator("any", shouldUseInputVariable = false) - - valueIfFound.isFalseConstant() && valueIfNotFound.isTrueConstant() -> return SimpleGenerator("none", shouldUseInputVariable = false) - - inputVariable.hasUsages(valueIfFound) -> { - if (!findFirst) return null // too dangerous because of side effects - - // specially handle the case when the result expression is "." or "?." - val qualifiedExpression = valueIfFound as? KtQualifiedExpression - if (qualifiedExpression != null) { - val receiver = qualifiedExpression.receiverExpression - val selector = qualifiedExpression.selectorExpression - if (receiver.isVariableReference(inputVariable) && selector != null && !inputVariable.hasUsages(selector)) { - return object: FindOperatorGenerator { - override val functionName: String - get() = "firstOrNull" - - override val shouldUseInputVariable: Boolean - get() = true - - override val chainCallCount: Int - get() = 2 - - override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression { - val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, filter) - return chainedCallGenerator.generate("$0", selector, receiver = findFirstCall, safeCall = true) - } - }.useElvisOperatorIfNeeded() - } - } - - // in case of nullable input variable we cannot distinguish by the result of "firstOrNull" whether nothing was found or 'null' was found - if (inputVariableCanHoldNull) return null - - return object: FindOperatorGenerator { - override val functionName: String - get() = "firstOrNull" - - override val shouldUseInputVariable: Boolean - get() = true - - override val chainCallCount: Int - get() = 2 // also includes "let" - - override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression { - val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, filter) - val letBody = generateLambda(inputVariable, valueIfFound) - return chainedCallGenerator.generate("let $0:'{}'", letBody, receiver = findFirstCall, safeCall = true) - } - }.useElvisOperatorIfNeeded() - } - - else -> { - return object: FindOperatorGenerator { - override val functionName: String - get() = "any" - - override val shouldUseInputVariable: Boolean - get() = false - - override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression { - val chainedCall = generateChainedCall(functionName, chainedCallGenerator, filter) - return KtPsiFactory(chainedCall).createExpressionByPattern("if ($0) $1 else $2", chainedCall, valueIfFound, valueIfNotFound) - } - } - } - } -} - -fun KtExpressionWithLabel.targetLoop(): KtLoopExpression? { - val label = getTargetLabel() - if (label == null) { - return parents.firstIsInstance() - } - else { - //TODO: does PARTIAL always work here? - return analyze(BodyResolveMode.PARTIAL)[BindingContext.LABEL_TARGET, label] as? KtLoopExpression - } -} - -fun KtExpression.isPlusPlusOf(): KtExpression? { - if (this !is KtUnaryExpression) return null - if (operationToken != KtTokens.PLUSPLUS) return null - return baseExpression -} - -fun KtExpression.previousStatement(): KtExpression? { - val statement = unwrapIfLabeled() - if (statement.parent !is KtBlockExpression) return null - return statement.siblings(forward = false, withItself = false).firstIsInstanceOrNull() -} - -fun KtExpression.nextStatement(): KtExpression? { - val statement = unwrapIfLabeled() - if (statement.parent !is KtBlockExpression) return null - return statement.siblings(forward = true, withItself = false).firstIsInstanceOrNull() -} - -fun KtExpression.unwrapIfLabeled(): KtExpression { - var statement = this - while (true) { - statement = statement.parent as? KtLabeledExpression ?: return statement - } -} - -fun KtLoopExpression.deleteWithLabels() { - unwrapIfLabeled().delete() -} - -fun PsiChildRange.withoutFirstStatement(): PsiChildRange { - val newFirst = first!!.siblings(forward = true, withItself = false).first { it !is PsiWhiteSpace } - return PsiChildRange(newFirst, last) -} - -fun PsiChildRange.withoutLastStatement(): PsiChildRange { - val newLast = last!!.siblings(forward = false, withItself = false).first { it !is PsiWhiteSpace } - return PsiChildRange(first, newLast) -} - data class VariableInitialization( val variable: KtProperty, val initializationStatement: KtExpression,