Minor: refactoring codeInliner package

This commit is contained in:
Dmitry Gridin
2019-03-27 16:15:25 +07:00
parent 16ae313b22
commit c343876f7c
7 changed files with 127 additions and 129 deletions
@@ -26,9 +26,9 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelectorOrThis
class ClassUsageReplacementStrategy(
typeReplacement: KtUserType?,
constructorReplacement: CodeToInline?,
project: Project
typeReplacement: KtUserType?,
constructorReplacement: CodeToInline?,
project: Project
) : UsageReplacementStrategy {
private val factory = KtPsiFactory(project)
@@ -45,8 +45,7 @@ class ClassUsageReplacementStrategy(
constructorReplacementStrategy?.createReplacer(usage)?.let { return it }
val parent = usage.parent
when (parent) {
when (val parent = usage.parent) {
is KtUserType -> {
if (typeReplacement == null) return null
return {
@@ -91,8 +90,7 @@ class ClassUsageReplacementStrategy(
val result = if (expressionToReplace != newExpression) {
expressionToReplace.replaced(newExpression)
}
else {
} else {
expressionToReplace
}
@@ -354,12 +354,11 @@ class CodeInliner<TCallElement : KtElement>(
return Argument(valueAssigned, bindingContext.getType(valueAssigned))
}
val resolvedArgument = resolvedCall.valueArguments[parameter]!!
when (resolvedArgument) {
when (val resolvedArgument = resolvedCall.valueArguments[parameter] ?: return null) {
is ExpressionValueArgument -> {
val valueArgument = resolvedArgument.valueArgument!!
val expression = valueArgument.getArgumentExpression()!!
expression.mark(USER_CODE_KEY)
val valueArgument = resolvedArgument.valueArgument
val expression = valueArgument?.getArgumentExpression()
expression?.mark(USER_CODE_KEY) ?: return null
if (valueArgument is LambdaArgument) {
expression.mark(WAS_FUNCTION_LITERAL_ARGUMENT_KEY)
}
@@ -447,9 +446,9 @@ class CodeInliner<TCallElement : KtElement>(
ShortenReferences { ShortenReferences.Options(removeThis = true) }.process(it, shortenFilter)
}
newElements.forEach {
newElements.forEach { element ->
// clean up user data
it.forEachDescendantOfType<KtExpression> {
element.forEachDescendantOfType<KtExpression> {
it.clear(USER_CODE_KEY)
it.clear(CodeToInline.PARAMETER_USAGE_KEY)
it.clear(CodeToInline.TYPE_PARAMETER_USAGE_KEY)
@@ -457,7 +456,7 @@ class CodeInliner<TCallElement : KtElement>(
it.clear(RECEIVER_VALUE_KEY)
it.clear(WAS_FUNCTION_LITERAL_ARGUMENT_KEY)
}
it.forEachDescendantOfType<KtValueArgument> {
element.forEachDescendantOfType<KtValueArgument> {
it.clear(MAKE_ARGUMENT_NAMED_KEY)
it.clear(DEFAULT_PARAMETER_VALUE_KEY)
}
@@ -45,17 +45,17 @@ import org.jetbrains.kotlin.utils.sure
import java.util.*
class CodeToInlineBuilder(
private val targetCallable: CallableDescriptor,
private val resolutionFacade: ResolutionFacade
private val targetCallable: CallableDescriptor,
private val resolutionFacade: ResolutionFacade
) {
private val psiFactory = KtPsiFactory(resolutionFacade.project)
//TODO: document that code will be modified
fun prepareCodeToInline(
mainExpression: KtExpression?,
statementsBefore: List<KtExpression>,
analyze: () -> BindingContext,
reformat: Boolean
mainExpression: KtExpression?,
statementsBefore: List<KtExpression>,
analyze: () -> BindingContext,
reformat: Boolean
): CodeToInline {
var bindingContext = analyze()
@@ -113,8 +113,8 @@ class CodeToInlineBuilder(
}
private fun needToAddParameterTypes(
lambdaExpression: KtLambdaExpression,
resolutionFacade: ResolutionFacade
lambdaExpression: KtLambdaExpression,
resolutionFacade: ResolutionFacade
): Boolean {
val functionLiteral = lambdaExpression.functionLiteral
val context = resolutionFacade.analyze(lambdaExpression, BodyResolveMode.PARTIAL_WITH_DIAGNOSTICS)
@@ -122,15 +122,19 @@ class CodeToInlineBuilder(
val factory = diagnostic.factory
val element = diagnostic.psiElement
val hasCantInferParameter = factory == Errors.CANNOT_INFER_PARAMETER_TYPE &&
element.parent.parent == functionLiteral
element.parent.parent == functionLiteral
val hasUnresolvedItOrThis = factory == Errors.UNRESOLVED_REFERENCE &&
element.text == "it" &&
element.getStrictParentOfType<KtFunctionLiteral>() == functionLiteral
element.text == "it" &&
element.getStrictParentOfType<KtFunctionLiteral>() == functionLiteral
hasCantInferParameter || hasUnresolvedItOrThis
}
}
private fun insertExplicitTypeArguments(codeToInline: MutableCodeToInline, bindingContext: BindingContext, analyze: () -> BindingContext): BindingContext {
private fun insertExplicitTypeArguments(
codeToInline: MutableCodeToInline,
bindingContext: BindingContext,
analyze: () -> BindingContext
): BindingContext {
val typeArgsToAdd = ArrayList<Pair<KtCallExpression, KtTypeArgumentList>>()
codeToInline.forEachDescendantOfType<KtCallExpression> {
if (InsertExplicitTypeArgumentsIntention.isApplicableTo(it, bindingContext)) {
@@ -162,8 +166,7 @@ class CodeToInlineBuilder(
if (expression.getReceiverExpression() == null) {
if (target is ValueParameterDescriptor && target.containingDeclaration == targetCallable) {
expression.putCopyableUserData(CodeToInline.PARAMETER_USAGE_KEY, target.name)
}
else if (target is TypeParameterDescriptor && target.containingDeclaration == targetCallable) {
} else if (target is TypeParameterDescriptor && target.containingDeclaration == targetCallable) {
expression.putCopyableUserData(CodeToInline.TYPE_PARAMETER_USAGE_KEY, target.name)
}
@@ -187,9 +190,13 @@ class CodeToInlineBuilder(
// add receivers in reverse order because arguments of a call were processed after the callee's name
for ((expr, receiverExpression) in receiversToAdd.asReversed()) {
val expressionToReplace = expr.parent as? KtCallExpression ?: expr
codeToInline.replaceExpression(expressionToReplace,
psiFactory.createExpressionByPattern("$0.$1", receiverExpression, expressionToReplace,
reformat = reformat))
codeToInline.replaceExpression(
expressionToReplace,
psiFactory.createExpressionByPattern(
"$0.$1", receiverExpression, expressionToReplace,
reformat = reformat
)
)
}
}
}
@@ -95,10 +95,10 @@ internal fun MutableCodeToInline.toNonMutable(): CodeToInline {
}
internal inline fun <reified T : PsiElement> MutableCodeToInline.collectDescendantsOfType(noinline predicate: (T) -> Boolean = { true }): List<T> {
return expressions.flatMap { it.collectDescendantsOfType<T>({ true }, predicate) }
return expressions.flatMap { it.collectDescendantsOfType({ true }, predicate) }
}
internal inline fun <reified T : PsiElement> MutableCodeToInline.forEachDescendantOfType(noinline action: (T) -> Unit) {
expressions.forEach { it.forEachDescendantOfType<T>(action) }
expressions.forEach { it.forEachDescendantOfType(action) }
}
@@ -34,8 +34,8 @@ import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments
import java.util.*
internal abstract class ReplacementPerformer<TElement : KtElement>(
protected val codeToInline: MutableCodeToInline,
protected var elementToBeReplaced: TElement
protected val codeToInline: MutableCodeToInline,
protected var elementToBeReplaced: TElement
) {
protected val psiFactory = KtPsiFactory(elementToBeReplaced)
@@ -43,8 +43,8 @@ internal abstract class ReplacementPerformer<TElement : KtElement>(
}
internal class AnnotationEntryReplacementPerformer(
codeToInline: MutableCodeToInline,
elementToBeReplaced: KtAnnotationEntry
codeToInline: MutableCodeToInline,
elementToBeReplaced: KtAnnotationEntry
) : ReplacementPerformer<KtAnnotationEntry>(codeToInline, elementToBeReplaced) {
override fun doIt(postProcessing: (PsiChildRange) -> PsiChildRange): KtAnnotationEntry {
@@ -63,21 +63,22 @@ internal class AnnotationEntryReplacementPerformer(
assert(range.first is KtAnnotationEntry)
val annotationEntry = range.first as KtAnnotationEntry
val text = annotationEntry.valueArguments.single().getArgumentExpression()!!.text
return annotationEntry.replaced(psiFactory.createAnnotationEntry("@" + text))
return annotationEntry.replaced(psiFactory.createAnnotationEntry("@$text"))
}
}
internal class ExpressionReplacementPerformer(
codeToInline: MutableCodeToInline,
expressionToBeReplaced: KtExpression
codeToInline: MutableCodeToInline,
expressionToBeReplaced: KtExpression
) : ReplacementPerformer<KtExpression>(codeToInline, expressionToBeReplaced) {
fun KtExpression.replacedWithStringTemplate(templateExpression: KtStringTemplateExpression): KtExpression? {
val parent = this.parent
return if (parent is KtStringTemplateEntryWithExpression
// Do not mix raw and non-raw templates
&& parent.parent.firstChild.text == templateExpression.firstChild.text) {
// Do not mix raw and non-raw templates
&& parent.parent.firstChild.text == templateExpression.firstChild.text
) {
val entriesToAdd = templateExpression.entries
val grandParentTemplateExpression = parent.parent as KtStringTemplateExpression
@@ -87,17 +88,16 @@ internal class ExpressionReplacementPerformer(
val nextElement = parent.nextSibling
if (lastNewEntry is KtSimpleNameStringTemplateEntry &&
lastNewEntry.expression != null &&
!canPlaceAfterSimpleNameEntry(nextElement)) {
!canPlaceAfterSimpleNameEntry(nextElement)
) {
lastNewEntry.replace(KtPsiFactory(this).createBlockStringTemplateEntry(lastNewEntry.expression!!))
}
grandParentTemplateExpression
}
else null
} else null
parent.delete()
result
}
else {
} else {
replaced(templateExpression)
}
}
@@ -129,8 +129,7 @@ internal class ExpressionReplacementPerformer(
if (canDropElementToBeReplaced) {
stub.delete()
null
}
else {
} else {
stub.replaced(psiFactory.createExpression("Unit"))
}
}
@@ -141,17 +140,14 @@ internal class ExpressionReplacementPerformer(
var range = if (replaced != null) {
if (insertedStatements.isEmpty()) {
PsiChildRange.singleElement(replaced)
}
else {
} else {
val statement = insertedStatements.first()
PsiChildRange(statement, replaced.parentsWithSelf.first { it.parent == statement.parent })
}
}
else {
} else {
if (insertedStatements.isEmpty()) {
PsiChildRange.EMPTY
}
else {
} else {
PsiChildRange(insertedStatements.first(), insertedStatements.last())
}
}
@@ -160,8 +156,7 @@ internal class ExpressionReplacementPerformer(
listener?.attach()
try {
range = postProcessing(range)
}
finally {
} finally {
listener?.detach()
}
@@ -214,8 +209,8 @@ internal class ExpressionReplacementPerformer(
val runAfterReplacement = elementToBeReplaced.replaced(runExpression)
val ktLambdaArgument = runAfterReplacement.lambdaArguments[0]
val block = ktLambdaArgument.getLambdaExpression()?.bodyExpression
?: throw KotlinExceptionWithAttachments("cant get body expression for $ktLambdaArgument")
.withAttachment("ktLambdaArgument", ktLambdaArgument.text)
?: throw KotlinExceptionWithAttachments("cant get body expression for $ktLambdaArgument")
.withAttachment("ktLambdaArgument", ktLambdaArgument.text)
elementToBeReplaced = block.statements.single()
return elementToBeReplaced
@@ -231,7 +226,7 @@ internal class ExpressionReplacementPerformer(
private fun <TElement : KtElement> withElementToBeReplacedPreserved(action: () -> TElement): TElement {
elementToBeReplaced.putCopyableUserData(ELEMENT_TO_BE_REPLACED_KEY, Unit)
val result = action()
elementToBeReplaced = result.findDescendantOfType<KtExpression> { it.getCopyableUserData(ELEMENT_TO_BE_REPLACED_KEY) != null }!!
elementToBeReplaced = result.findDescendantOfType { it.getCopyableUserData(ELEMENT_TO_BE_REPLACED_KEY) != null }!!
elementToBeReplaced.putCopyableUserData(ELEMENT_TO_BE_REPLACED_KEY, null)
return result
}
@@ -51,72 +51,72 @@ interface UsageReplacementStrategy {
private val LOG = Logger.getInstance(UsageReplacementStrategy::class.java)
fun UsageReplacementStrategy.replaceUsagesInWholeProject(
targetPsiElement: PsiElement,
progressTitle: String,
commandName: String,
postAction: () -> Unit = {}
targetPsiElement: PsiElement,
progressTitle: String,
commandName: String,
postAction: () -> Unit = {}
) {
val project = targetPsiElement.project
ProgressManager.getInstance().run(
object : Task.Modal(project, progressTitle, true) {
override fun run(indicator: ProgressIndicator) {
val usages = runReadAction {
val searchScope = KotlinSourceFilterScope.projectSources(GlobalSearchScope.projectScope(project), project)
ReferencesSearch.search(targetPsiElement, searchScope)
.filterIsInstance<KtSimpleNameReference>()
.map { ref -> ref.expression }
}
this@replaceUsagesInWholeProject.replaceUsages(usages, targetPsiElement, project, commandName, postAction)
object : Task.Modal(project, progressTitle, true) {
override fun run(indicator: ProgressIndicator) {
val usages = runReadAction {
val searchScope = KotlinSourceFilterScope.projectSources(GlobalSearchScope.projectScope(project), project)
ReferencesSearch.search(targetPsiElement, searchScope)
.filterIsInstance<KtSimpleNameReference>()
.map { ref -> ref.expression }
}
})
this@replaceUsagesInWholeProject.replaceUsages(usages, targetPsiElement, project, commandName, postAction)
}
})
}
fun UsageReplacementStrategy.replaceUsages(
usages: Collection<KtSimpleNameExpression>,
targetPsiElement: PsiElement,
project: Project,
commandName: String,
postAction: () -> Unit = {}
usages: Collection<KtSimpleNameExpression>,
targetPsiElement: PsiElement,
project: Project,
commandName: String,
postAction: () -> Unit = {}
) {
GuiUtils.invokeLaterIfNeeded({
project.executeWriteCommand(commandName) {
val targetDeclaration = targetPsiElement as? KtNamedDeclaration
project.executeWriteCommand(commandName) {
val targetDeclaration = targetPsiElement as? KtNamedDeclaration
val usagesByFile = usages.groupBy { it.containingFile }
val usagesByFile = usages.groupBy { it.containingFile }
val KEY = Key<Unit>("UsageReplacementStrategy.replaceUsages")
val KEY = Key<Unit>("UsageReplacementStrategy.replaceUsages")
for ((file, usagesInFile) in usagesByFile) {
usagesInFile.forEach { it.putCopyableUserData(KEY, Unit) }
for ((file, usagesInFile) in usagesByFile) {
usagesInFile.forEach { it.putCopyableUserData(KEY, Unit) }
// we should delete imports later to not affect other usages
val importsToDelete = mutableListOf<KtImportDirective>()
// we should delete imports later to not affect other usages
val importsToDelete = mutableListOf<KtImportDirective>()
var usagesToProcess = usagesInFile
while (usagesToProcess.isNotEmpty()) {
if (processUsages(usagesToProcess, targetDeclaration, importsToDelete)) break
var usagesToProcess = usagesInFile
while (usagesToProcess.isNotEmpty()) {
if (processUsages(usagesToProcess, targetDeclaration, importsToDelete)) break
// some usages may get invalidated we need to find them in the tree
usagesToProcess = file.collectDescendantsOfType<KtSimpleNameExpression> { it.getCopyableUserData(KEY) != null }
}
// some usages may get invalidated we need to find them in the tree
usagesToProcess = file.collectDescendantsOfType { it.getCopyableUserData(KEY) != null }
}
file.forEachDescendantOfType<KtSimpleNameExpression> { it.putCopyableUserData(KEY, null) }
importsToDelete.forEach { it.delete() }
}
file.forEachDescendantOfType<KtSimpleNameExpression> { it.putCopyableUserData(KEY, null) }
postAction()
}
}, ModalityState.NON_MODAL)
importsToDelete.forEach { it.delete() }
}
postAction()
}
}, ModalityState.NON_MODAL)
}
/**
* @return false if some usages were invalidated
*/
private fun UsageReplacementStrategy.processUsages(
usages: List<KtSimpleNameExpression>,
targetDeclaration: KtNamedDeclaration?,
importsToDelete: MutableList<KtImportDirective>
usages: List<KtSimpleNameExpression>,
targetDeclaration: KtNamedDeclaration?,
importsToDelete: MutableList<KtImportDirective>
): Boolean {
var invalidUsagesFound = false
for (usage in usages) {
@@ -138,17 +138,18 @@ private fun UsageReplacementStrategy.processUsages(
}
createReplacer(usage)?.invoke()
}
catch (e: Throwable) {
} catch (e: Throwable) {
LOG.error(e)
}
}
return !invalidUsagesFound
}
private fun UsageReplacementStrategy.specialUsageProcessing(usage: KtSimpleNameExpression, targetDeclaration: KtNamedDeclaration?): Boolean {
val usageParent = usage.parent
when (usageParent) {
private fun UsageReplacementStrategy.specialUsageProcessing(
usage: KtSimpleNameExpression,
targetDeclaration: KtNamedDeclaration?
): Boolean {
when (val usageParent = usage.parent) {
is KtCallableReferenceExpression -> {
val grandParent = usageParent.parent
ConvertReferenceToLambdaIntention().applyTo(usageParent, null)
@@ -166,7 +167,7 @@ private fun UsageReplacementStrategy.specialUsageProcessing(usage: KtSimpleNameE
for (lambdaArgument in lambdaArguments) {
val lambdaExpression = lambdaArgument.getLambdaExpression() ?: continue
val functionDescriptor =
lambdaExpression.functionLiteral.resolveToDescriptorIfAny() as? FunctionDescriptor ?: continue
lambdaExpression.functionLiteral.resolveToDescriptorIfAny() as? FunctionDescriptor ?: continue
if (functionDescriptor.valueParameters.isNotEmpty()) {
specifySignature.applyTo(lambdaExpression, null)
}
@@ -183,7 +184,7 @@ private fun UsageReplacementStrategy.specialUsageProcessing(usage: KtSimpleNameE
}
private fun UsageReplacementStrategy.doRefactoringInside(
element: KtElement, targetName: String?, targetDescriptor: DeclarationDescriptor?
element: KtElement, targetName: String?, targetDescriptor: DeclarationDescriptor?
) {
element.forEachDescendantOfType<KtSimpleNameExpression> { usage ->
if (usage.isValid && usage.getReferencedName() == targetName) {
@@ -50,12 +50,12 @@ import org.jetbrains.kotlin.types.KotlinType
* @param safeCall If true, then the whole code must not be executed if the [value] evaluates to null.
*/
internal fun MutableCodeToInline.introduceValue(
value: KtExpression,
valueType: KotlinType?,
usages: Collection<KtExpression>,
expressionToBeReplaced: KtExpression,
nameSuggestion: String? = null,
safeCall: Boolean = false
value: KtExpression,
valueType: KotlinType?,
usages: Collection<KtExpression>,
expressionToBeReplaced: KtExpression,
nameSuggestion: String? = null,
safeCall: Boolean = false
) {
assert(usages.all { this.containsStrictlyInside(it) })
@@ -100,12 +100,10 @@ internal fun MutableCodeToInline.introduceValue(
}
replaceUsages(name)
}
else {
} else {
statementsBefore.add(0, value)
}
}
else {
} else {
val useIt = !isNameUsed("it")
val name = if (useIt) Name.identifier("it") else suggestName { !isNameUsed(it) }
replaceUsages(name)
@@ -139,17 +137,17 @@ fun String.nameHasConflictsInScope(lexicalScope: LexicalScope) = lexicalScope.ge
}
private fun variableNeedsExplicitType(
initializer: KtExpression,
initializerType: KotlinType,
context: KtExpression,
resolutionScope: LexicalScope,
bindingContext: BindingContext
initializer: KtExpression,
initializerType: KotlinType,
context: KtExpression,
resolutionScope: LexicalScope,
bindingContext: BindingContext
): Boolean {
if (ErrorUtils.containsErrorType(initializerType)) return false
val valueTypeWithoutExpectedType = initializer.computeTypeInContext(
resolutionScope,
context,
dataFlowInfo = bindingContext.getDataFlowInfoBefore(context)
resolutionScope,
context,
dataFlowInfo = bindingContext.getDataFlowInfoBefore(context)
)
return valueTypeWithoutExpectedType == null || ErrorUtils.containsErrorType(valueTypeWithoutExpectedType)
}