Anti-KNPE refactoring of RedundantSamConstructorInspection

So EA-84354 fixed
This commit is contained in:
Mikhail Glukhikh
2017-03-22 15:11:54 +03:00
parent 6f4ae2585b
commit 4687a867c3
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.java.descriptors.SamAdapterDescriptor
import org.jetbrains.kotlin.load.java.descriptors.SamConstructorDescriptor
import org.jetbrains.kotlin.load.java.sam.SingleAbstractMethodUtils
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
@@ -46,6 +47,7 @@ import org.jetbrains.kotlin.resolve.scopes.SyntheticScopes
import org.jetbrains.kotlin.resolve.scopes.collectSyntheticMemberFunctions
import org.jetbrains.kotlin.synthetic.SamAdapterExtensionFunctionDescriptor
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.utils.keysToMapExceptNulls
class RedundantSamConstructorInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
@@ -60,7 +62,7 @@ class RedundantSamConstructorInspection : AbstractKotlinInspection() {
}
}
private fun createQuickFix(expressions: List<KtCallExpression>): LocalQuickFix {
private fun createQuickFix(expressions: Collection<KtCallExpression>): LocalQuickFix {
return object : LocalQuickFix {
override fun getName() = "Remove redundant SAM-constructors"
override fun getFamilyName() = name
@@ -77,11 +79,12 @@ class RedundantSamConstructorInspection : AbstractKotlinInspection() {
val samConstructorCalls = samConstructorCallsToBeConverted(expression)
if (samConstructorCalls.isEmpty()) return
if (samConstructorCalls.size == 1) {
val single = samConstructorCalls.single()
val single = samConstructorCalls.singleOrNull()
if (single != null) {
val calleeExpression = single.calleeExpression ?: return
val problemDescriptor = holder.manager.
createProblemDescriptor(single.calleeExpression!!,
single.typeArgumentList ?: single.calleeExpression!!,
createProblemDescriptor(calleeExpression,
single.typeArgumentList ?: calleeExpression,
"Redundant SAM-constructor",
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
isOnTheFly,
@@ -110,7 +113,8 @@ class RedundantSamConstructorInspection : AbstractKotlinInspection() {
return callExpression.getQualifiedExpressionForSelectorOrThis().replace(functionalArgument) as KtLambdaExpression
}
private fun canBeReplaced(parentCall: KtCallExpression, samConstructorArguments: List<KtValueArgument>): Boolean {
private fun canBeReplaced(parentCall: KtCallExpression,
samConstructorCallArgumentMap: Map<KtValueArgument, KtCallExpression>): Boolean {
val context = parentCall.analyze(BodyResolveMode.PARTIAL)
val calleeExpression = parentCall.calleeExpression ?: return false
@@ -120,7 +124,7 @@ class RedundantSamConstructorInspection : AbstractKotlinInspection() {
val dataFlow = context.getDataFlowInfoBefore(parentCall)
val callResolver = parentCall.getResolutionFacade().frontendService<CallResolver>()
val newCall = CallWithConvertedArguments(originalCall.call, samConstructorArguments)
val newCall = CallWithConvertedArguments(originalCall.call, samConstructorCallArgumentMap)
val qualifiedExpression = parentCall.getQualifiedExpressionForSelectorOrThis()
val expectedType = context[BindingContext.EXPECTED_EXPRESSION_TYPE, qualifiedExpression] ?: TypeUtils.NO_EXPECTED_TYPE
@@ -133,14 +137,17 @@ class RedundantSamConstructorInspection : AbstractKotlinInspection() {
return samAdapterOriginalDescriptor.original == originalCall.resultingDescriptor.original
}
private class CallWithConvertedArguments(original: Call, val argumentsToConvert: Collection<ValueArgument>): DelegatingCall(original) {
private class CallWithConvertedArguments(
original: Call,
val callArgumentMapToConvert: Map<KtValueArgument, KtCallExpression>
) : DelegatingCall(original) {
private val newArguments: List<ValueArgument>
init {
val factory = KtPsiFactory(callElement)
newArguments = original.valueArguments.map { argument ->
if (argument !in argumentsToConvert) return@map argument
val newExpression = argument.toCallExpression()!!.samConstructorValueArgument()!!.getArgumentExpression()!!
val call = callArgumentMapToConvert[argument]
val newExpression = call?.samConstructorValueArgument()?.getArgumentExpression() ?: return@map argument
factory.createArgument(newExpression, argument.getArgumentName()?.asName)
}
}
@@ -148,12 +155,9 @@ class RedundantSamConstructorInspection : AbstractKotlinInspection() {
override fun getValueArguments() = newArguments
}
fun samConstructorCallsToBeConverted(functionCall: KtCallExpression): List<KtCallExpression> {
return samConstructorArgumentsToBeConverted(functionCall).map { it.toCallExpression()!! }
}
private fun samConstructorArgumentsToBeConverted(functionCall: KtCallExpression): List<KtValueArgument> {
if (functionCall.valueArguments.all { !canBeSamConstructorCall(it) }) {
fun samConstructorCallsToBeConverted(functionCall: KtCallExpression): Collection<KtCallExpression> {
val valueArguments = functionCall.valueArguments
if (valueArguments.all { !canBeSamConstructorCall(it) }) {
return emptyList()
}
@@ -161,22 +165,29 @@ class RedundantSamConstructorInspection : AbstractKotlinInspection() {
val functionResolvedCall = functionCall.getResolvedCall(bindingContext) ?: return emptyList()
if (!functionResolvedCall.isReallySuccess()) return emptyList()
val samConstructorCallArguments = functionCall.valueArguments.filter {
it.toCallExpression()?.getResolvedCall(bindingContext)?.resultingDescriptor?.original is SamConstructorDescriptor
val samConstructorCallArgumentMap = valueArguments.keysToMapExceptNulls { it.toCallExpression() }.filter {
(_, call) -> call.getResolvedCall(bindingContext)?.resultingDescriptor?.original is SamConstructorDescriptor
}
if (samConstructorCallArguments.isEmpty()) return emptyList()
if (samConstructorCallArgumentMap.isEmpty()) return emptyList()
if (samConstructorCallArguments.any { hasLabeledReturnPreventingConversion(it.toCallExpression()!!) }) return emptyList()
if (samConstructorCallArgumentMap.values.any {
val samValueArgument = it.samConstructorValueArgument()
val samConstructorName = (it.calleeExpression as? KtSimpleNameExpression)?.getReferencedNameAsName()
samValueArgument == null || samConstructorName == null ||
samValueArgument.hasLabeledReturnPreventingConversion(samConstructorName)
}) return emptyList()
val originalFunctionDescriptor = functionResolvedCall.resultingDescriptor.original as? FunctionDescriptor ?: return emptyList()
val containingClass = originalFunctionDescriptor.containingDeclaration as? ClassDescriptor ?: return emptyList()
// SAM adapters for static functions
for (staticFunWithSameName in containingClass.staticScope.getContributedFunctions(functionResolvedCall.resultingDescriptor.name, NoLookupLocation.FROM_IDE)) {
val contributedFunctions = containingClass.staticScope.getContributedFunctions(
functionResolvedCall.resultingDescriptor.name, NoLookupLocation.FROM_IDE)
for (staticFunWithSameName in contributedFunctions) {
if (staticFunWithSameName is SamAdapterDescriptor<*>) {
if (isSamAdapterSuitableForCall(staticFunWithSameName, originalFunctionDescriptor, samConstructorCallArguments.size)) {
return samConstructorCallArguments.takeIf { canBeReplaced(functionCall, it) } ?: emptyList()
if (isSamAdapterSuitableForCall(staticFunWithSameName, originalFunctionDescriptor, samConstructorCallArgumentMap.size)) {
return samConstructorCallArgumentMap.takeIf { canBeReplaced(functionCall, it) }?.values ?: emptyList()
}
}
}
@@ -189,8 +200,8 @@ class RedundantSamConstructorInspection : AbstractKotlinInspection() {
NoLookupLocation.FROM_IDE)
for (syntheticExtension in syntheticExtensions) {
val samAdapter = syntheticExtension as? SamAdapterExtensionFunctionDescriptor ?: continue
if (isSamAdapterSuitableForCall(samAdapter, originalFunctionDescriptor, samConstructorCallArguments.size)) {
return samConstructorCallArguments.takeIf { canBeReplaced(functionCall, it) } ?: emptyList()
if (isSamAdapterSuitableForCall(samAdapter, originalFunctionDescriptor, samConstructorCallArgumentMap.size)) {
return samConstructorCallArgumentMap.takeIf { canBeReplaced(functionCall, it) }?.values ?: emptyList()
}
}
@@ -227,10 +238,7 @@ class RedundantSamConstructorInspection : AbstractKotlinInspection() {
return parametersWithSamTypeCount == samConstructorsCount
}
private fun hasLabeledReturnPreventingConversion(samConstructorCall: KtCallExpression): Boolean {
val argument = samConstructorCall.samConstructorValueArgument()!!
val samConstructorName = (samConstructorCall.calleeExpression as KtSimpleNameExpression).getReferencedNameAsName()
return argument.anyDescendantOfType<KtReturnExpression> { it.getLabelNameAsName() == samConstructorName }
}
private fun KtValueArgument.hasLabeledReturnPreventingConversion(samConstructorName: Name) =
anyDescendantOfType<KtReturnExpression> { it.getLabelNameAsName() == samConstructorName }
}
}