Checking that input variable is really used

This commit is contained in:
Valentin Kipyatkov
2016-04-20 17:22:34 +03:00
parent 345f2b8a52
commit e0a7e1dbed
8 changed files with 60 additions and 5 deletions
@@ -51,9 +51,11 @@ interface Transformation {
fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression
val chainCallCount: Int val chainCallCount: Int
get() = 1 get() = 1
val shouldUseInputVariable: Boolean
get() = true
} }
/** /**
@@ -71,9 +71,17 @@ fun match(loop: KtForExpression): ResultTransformationMatch? {
state = state.copy(statements = block.statements) state = state.copy(statements = block.statements)
} }
val inputVariableUsed = state.inputVariable.hasUsages(state.statements)
for (matcher in MatcherRegistrar.resultMatchers) { for (matcher in MatcherRegistrar.resultMatchers) {
val match = matcher.match(state) val match = matcher.match(state)
if (match != null) { if (match != null) {
if (!inputVariableUsed
&& (match.sequenceTransformations.any { it.shouldUseInputVariable }
|| match.resultTransformation.shouldUseInputVariable)) {
return null
}
sequenceTransformations.addAll(match.sequenceTransformations) sequenceTransformations.addAll(match.sequenceTransformations)
return ResultTransformationMatch(match.resultTransformation, sequenceTransformations) return ResultTransformationMatch(match.resultTransformation, sequenceTransformations)
.let { mergeTransformations(it) } .let { mergeTransformations(it) }
@@ -84,6 +92,10 @@ fun match(loop: KtForExpression): ResultTransformationMatch? {
for (matcher in MatcherRegistrar.sequenceMatchers) { for (matcher in MatcherRegistrar.sequenceMatchers) {
val match = matcher.match(state) val match = matcher.match(state)
if (match != null) { if (match != null) {
if (!inputVariableUsed && match.transformations.any { it.shouldUseInputVariable }) {
return null
}
val newState = match.newState val newState = match.newState
// check that old input variable is not needed anymore // check that old input variable is not needed anymore
if (state.inputVariable != newState.inputVariable && state.inputVariable.hasUsages(newState.statements)) return null if (state.inputVariable != newState.inputVariable && state.inputVariable.hasUsages(newState.statements)) return null
@@ -42,6 +42,9 @@ class CountTransformation(
override val presentation: String override val presentation: String
get() = "count" + (if (filter != null) "{}" else "()") get() = "count" + (if (filter != null) "{}" else "()")
override val shouldUseInputVariable: Boolean
get() = false
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
val call = if (filter != null) { val call = if (filter != null) {
val lambda = generateLambda(inputVariable, filter) val lambda = generateLambda(inputVariable, filter)
@@ -46,6 +46,9 @@ class FindAndAssignTransformation(
override val chainCallCount: Int override val chainCallCount: Int
get() = generator.chainCallCount get() = generator.chainCallCount
override val shouldUseInputVariable: Boolean
get() = generator.shouldUseInputVariable
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
return generator.generate(chainedCallGenerator, filter) return generator.generate(chainedCallGenerator, filter)
} }
@@ -50,6 +50,9 @@ class FindAndReturnTransformation(
override val chainCallCount: Int override val chainCallCount: Int
get() = generator.chainCallCount get() = generator.chainCallCount
override val shouldUseInputVariable: Boolean
get() = generator.shouldUseInputVariable
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
return generator.generate(chainedCallGenerator, filter) return generator.generate(chainedCallGenerator, filter)
} }
@@ -115,6 +115,8 @@ fun KtProperty.hasWriteUsages(): Boolean {
interface FindOperatorGenerator { interface FindOperatorGenerator {
val functionName: String val functionName: String
val shouldUseInputVariable: Boolean
fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression
val chainCallCount: Int val chainCallCount: Int
@@ -140,7 +142,7 @@ fun buildFindOperationGenerator(
} }
} }
class SimpleGenerator(override val functionName: String) : FindOperatorGenerator { class SimpleGenerator(override val functionName: String, override val shouldUseInputVariable: Boolean) : FindOperatorGenerator {
override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression { override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression {
return generateChainedCall(functionName, chainedCallGenerator, filter) return generateChainedCall(functionName, chainedCallGenerator, filter)
} }
@@ -164,13 +166,13 @@ fun buildFindOperationGenerator(
when { when {
valueIfFound.isVariableReference(inputVariable) -> { valueIfFound.isVariableReference(inputVariable) -> {
val generator = SimpleGenerator(if (findFirst) "firstOrNull" else "lastOrNull") val generator = SimpleGenerator(if (findFirst) "firstOrNull" else "lastOrNull", shouldUseInputVariable = true)
return generator.useElvisOperatorIfNeeded() return generator.useElvisOperatorIfNeeded()
} }
valueIfFound.isTrueConstant() && valueIfNotFound.isFalseConstant() -> return SimpleGenerator("any") valueIfFound.isTrueConstant() && valueIfNotFound.isFalseConstant() -> return SimpleGenerator("any", shouldUseInputVariable = false)
valueIfFound.isFalseConstant() && valueIfNotFound.isTrueConstant() -> return SimpleGenerator("none") valueIfFound.isFalseConstant() && valueIfNotFound.isTrueConstant() -> return SimpleGenerator("none", shouldUseInputVariable = false)
inputVariable.hasUsages(valueIfFound) -> { inputVariable.hasUsages(valueIfFound) -> {
if (!findFirst) return null // too dangerous because of side effects if (!findFirst) return null // too dangerous because of side effects
@@ -185,6 +187,9 @@ fun buildFindOperationGenerator(
override val functionName: String override val functionName: String
get() = "firstOrNull" get() = "firstOrNull"
override val shouldUseInputVariable: Boolean
get() = true
override val chainCallCount: Int override val chainCallCount: Int
get() = 2 get() = 2
@@ -203,6 +208,9 @@ fun buildFindOperationGenerator(
override val functionName: String override val functionName: String
get() = "firstOrNull" get() = "firstOrNull"
override val shouldUseInputVariable: Boolean
get() = true
override val chainCallCount: Int override val chainCallCount: Int
get() = 2 // also includes "let" get() = 2 // also includes "let"
@@ -219,6 +227,9 @@ fun buildFindOperationGenerator(
override val functionName: String override val functionName: String
get() = "any" get() = "any"
override val shouldUseInputVariable: Boolean
get() = false
override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression { override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression {
val chainedCall = generateChainedCall(functionName, chainedCallGenerator, filter) val chainedCall = generateChainedCall(functionName, chainedCallGenerator, filter)
return KtPsiFactory(chainedCall).createExpressionByPattern("if ($0) $1 else $2", chainedCall, valueIfFound, valueIfNotFound) return KtPsiFactory(chainedCall).createExpressionByPattern("if ($0) $1 else $2", chainedCall, valueIfFound, valueIfNotFound)
@@ -0,0 +1,13 @@
// WITH_RUNTIME
// IS_APPLICABLE: false
import java.util.*
fun foo(list: List<String>): String? {
val random = Random()
for (s in list) {
if (random.nextBoolean()) {
return s
}
}
return null
}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
// IS_APPLICABLE: false
fun foo(list: List<String>, target: MutableList<Int>) {
<caret>for (s in list) {
if (s.length > 0)
target.add(0)
}
}