Do not generate fields for unused suspend lambda parameters
This also allows us to not spill them in the lambda. But, disable this optimization for local named functions. #KT-16222 In progress
This commit is contained in:
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitor
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.KtElementInstruction
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.*
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.*
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.LocalFunctionDeclarationInstruction
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.MarkInstruction
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.VariableDeclarationInstruction
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.sideEffectFree
|
||||
@@ -24,6 +25,8 @@ import org.jetbrains.kotlin.cfg.variable.VariableUseState.*
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
@@ -43,7 +46,9 @@ import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
|
||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsResultOfLambda
|
||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsStatement
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.checkers.findDestructuredVariable
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getDispatchReceiverWithSmartCast
|
||||
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.hasThisOrNoDispatchReceiver
|
||||
import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject
|
||||
@@ -51,6 +56,7 @@ import org.jetbrains.kotlin.resolve.calls.util.isSingleUnderscore
|
||||
import org.jetbrains.kotlin.resolve.checkers.PlatformDiagnosticSuppressor
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeUtils.*
|
||||
import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils
|
||||
@@ -101,6 +107,8 @@ class ControlFlowInformationProvider private constructor(
|
||||
markUnusedVariables()
|
||||
}
|
||||
|
||||
checkForSuspendLambdaAndMarkParameters(pseudocode)
|
||||
|
||||
markStatements()
|
||||
markAnnotationArguments()
|
||||
|
||||
@@ -808,6 +816,88 @@ class ControlFlowInformationProvider private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkForSuspendLambdaAndMarkParameters(pseudocode: Pseudocode) {
|
||||
for (instruction in pseudocode.instructionsIncludingDeadCode) {
|
||||
if (instruction is LocalFunctionDeclarationInstruction) {
|
||||
val psi = instruction.body.correspondingElement
|
||||
if (psi is KtFunctionLiteral) {
|
||||
val descriptor = trace.bindingContext[DECLARATION_TO_DESCRIPTOR, psi]
|
||||
if (descriptor is AnonymousFunctionDescriptor && descriptor.isSuspend) {
|
||||
markReadOfSuspendLambdaParameters(instruction.body)
|
||||
continue
|
||||
}
|
||||
}
|
||||
checkForSuspendLambdaAndMarkParameters(instruction.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun markReadOfSuspendLambdaParameters(pseudocode: Pseudocode) {
|
||||
val instructions = pseudocode.instructionsIncludingDeadCode
|
||||
for (instruction in instructions) {
|
||||
if (instruction is LocalFunctionDeclarationInstruction) {
|
||||
markReadOfSuspendLambdaParameters(instruction.body)
|
||||
continue
|
||||
}
|
||||
markReadOfSuspendLambdaParameter(instruction)
|
||||
markImplicitReceiverOfSuspendLambda(instruction)
|
||||
}
|
||||
}
|
||||
|
||||
private fun markReadOfSuspendLambdaParameter(instruction: Instruction) {
|
||||
if (instruction !is ReadValueInstruction) return
|
||||
val target = instruction.target as? AccessTarget.Call ?: return
|
||||
val descriptor = target.resolvedCall.resultingDescriptor
|
||||
if (descriptor is ParameterDescriptor) {
|
||||
val containing = descriptor.containingDeclaration
|
||||
if (containing is AnonymousFunctionDescriptor && containing.isSuspend) {
|
||||
trace.record(SUSPEND_LAMBDA_PARAMETER_USED, containing to descriptor.index())
|
||||
}
|
||||
} else if (descriptor is LocalVariableDescriptor) {
|
||||
val containing = descriptor.containingDeclaration
|
||||
if (containing is AnonymousFunctionDescriptor && containing.isSuspend) {
|
||||
findDestructuredVariable(descriptor, containing)?.let {
|
||||
trace.record(SUSPEND_LAMBDA_PARAMETER_USED, containing to it.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun markImplicitReceiverOfSuspendLambda(instruction: Instruction) {
|
||||
if (instruction !is MagicInstruction || instruction.kind != MagicKind.IMPLICIT_RECEIVER) return
|
||||
|
||||
fun CallableDescriptor?.markIfNeeded() {
|
||||
if (this is AnonymousFunctionDescriptor && isSuspend) {
|
||||
trace.record(SUSPEND_LAMBDA_PARAMETER_USED, this to -1)
|
||||
}
|
||||
}
|
||||
|
||||
if (instruction.element is KtDestructuringDeclarationEntry || instruction.element is KtCallExpression) {
|
||||
val visited = mutableSetOf<Instruction>()
|
||||
fun dfs(insn: Instruction) {
|
||||
if (!visited.add(insn)) return
|
||||
if (insn is CallInstruction && insn.element == instruction.element) {
|
||||
for ((_, receiver) in insn.receiverValues) {
|
||||
(receiver as? ExtensionReceiver)?.declarationDescriptor?.apply { markIfNeeded() }
|
||||
}
|
||||
}
|
||||
for (next in insn.nextInstructions) {
|
||||
dfs(next)
|
||||
}
|
||||
}
|
||||
|
||||
instruction.next?.let { dfs(it) }
|
||||
} else if (instruction.element is KtNameReferenceExpression) {
|
||||
val call = instruction.element.getResolvedCall(trace.bindingContext)
|
||||
if (call is VariableAsFunctionResolvedCall) {
|
||||
(call.variableCall.dispatchReceiver as? ExtensionReceiver)?.declarationDescriptor?.apply { markIfNeeded() }
|
||||
(call.variableCall.extensionReceiver as? ExtensionReceiver)?.declarationDescriptor?.apply { markIfNeeded() }
|
||||
}
|
||||
(call?.dispatchReceiver as? ExtensionReceiver)?.declarationDescriptor?.apply { markIfNeeded() }
|
||||
(call?.extensionReceiver as? ExtensionReceiver)?.declarationDescriptor?.apply { markIfNeeded() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun markAnnotationArguments() {
|
||||
if (subroutine is KtAnnotationEntry) {
|
||||
markAnnotationArguments(subroutine)
|
||||
@@ -1219,3 +1309,10 @@ class ControlFlowInformationProvider private constructor(
|
||||
|| diagnosticFactory === UNUSED_CHANGED_VALUE
|
||||
}
|
||||
}
|
||||
|
||||
fun ParameterDescriptor.index(): Int =
|
||||
when (this) {
|
||||
is ReceiverParameterDescriptor -> -1
|
||||
is ValueParameterDescriptor -> index
|
||||
else -> error("expected either receiver or value parameter, but got: $this")
|
||||
}
|
||||
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.resolve;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import kotlin.Pair;
|
||||
import kotlin.annotations.jvm.ReadOnly;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -18,13 +19,13 @@ import org.jetbrains.kotlin.contracts.description.InvocationKind;
|
||||
import org.jetbrains.kotlin.contracts.model.Computation;
|
||||
import org.jetbrains.kotlin.descriptors.*;
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor;
|
||||
import org.jetbrains.kotlin.name.FqName;
|
||||
import org.jetbrains.kotlin.name.FqNameUnsafe;
|
||||
import org.jetbrains.kotlin.psi.*;
|
||||
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext;
|
||||
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemCompleter;
|
||||
import org.jetbrains.kotlin.resolve.calls.model.PartialCallContainer;
|
||||
import org.jetbrains.kotlin.resolve.calls.model.PartialCallResolutionResult;
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
|
||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo;
|
||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue;
|
||||
@@ -169,6 +170,8 @@ public interface BindingContext {
|
||||
WritableSlice<KtExpression, Boolean> VARIABLE_REASSIGNMENT = Slices.createSimpleSetSlice();
|
||||
WritableSlice<ValueParameterDescriptor, Boolean> AUTO_CREATED_IT = Slices.createSimpleSetSlice();
|
||||
|
||||
WritableSlice<Pair<AnonymousFunctionDescriptor, Integer>, Boolean> SUSPEND_LAMBDA_PARAMETER_USED = Slices.createSimpleSlice();
|
||||
|
||||
/**
|
||||
* Has type of current expression has been already resolved
|
||||
*/
|
||||
|
||||
+8
-7
@@ -103,7 +103,7 @@ class CapturingInClosureChecker : CallChecker {
|
||||
// We cannot box function/lambda arguments, destructured lambda arguments, for-loop index variables,
|
||||
// exceptions inside catch blocks ans vals in when
|
||||
return if (isArgument(variable, variableParent) ||
|
||||
isDestructuredVariable(variable, variableParent) ||
|
||||
findDestructuredVariable(variable, variableParent) != null ||
|
||||
isForLoopParameter(variable) ||
|
||||
isCatchBlockParameter(variable) ||
|
||||
isValInWhen(variable)
|
||||
@@ -118,12 +118,6 @@ class CapturingInClosureChecker : CallChecker {
|
||||
variable is ValueParameterDescriptor && variableParent is CallableDescriptor
|
||||
&& variableParent.valueParameters.contains(variable)
|
||||
|
||||
private fun isDestructuredVariable(variable: VariableDescriptor, variableParent: DeclarationDescriptor): Boolean =
|
||||
variable is LocalVariableDescriptor && variableParent is AnonymousFunctionDescriptor &&
|
||||
variableParent.valueParameters.any {
|
||||
it is ValueParameterDescriptorImpl.WithDestructuringDeclaration && it.destructuringVariables.contains(variable)
|
||||
}
|
||||
|
||||
private fun isValInWhen(variable: VariableDescriptor): Boolean {
|
||||
val psi = ((variable as? LocalVariableDescriptor)?.source as? KotlinSourceElement)?.psi ?: return false
|
||||
return (psi.parent as? KtWhenExpression)?.let { it.subjectVariable == psi } == true
|
||||
@@ -178,3 +172,10 @@ class CapturingInClosureChecker : CallChecker {
|
||||
return getCalleeDescriptorAndParameter(bindingContext, argument)?.second?.isCrossinline == true
|
||||
}
|
||||
}
|
||||
|
||||
fun findDestructuredVariable(variable: VariableDescriptor, variableParent: DeclarationDescriptor): ValueParameterDescriptor? =
|
||||
if (variable is LocalVariableDescriptor && variableParent is AnonymousFunctionDescriptor) {
|
||||
variableParent.valueParameters.find {
|
||||
it is ValueParameterDescriptorImpl.WithDestructuringDeclaration && it.destructuringVariables.contains(variable)
|
||||
}
|
||||
} else null
|
||||
|
||||
Reference in New Issue
Block a user