Fix build in bunch 191

This commit is contained in:
Andrey Uskov
2018-12-12 18:00:30 +03:00
parent 201522c6c8
commit 595e75e059
5 changed files with 739 additions and 0 deletions
@@ -0,0 +1,69 @@
/*
* 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.cli.jvm.compiler
import com.intellij.codeInsight.ExternalAnnotationsManager
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.*
class MockExternalAnnotationsManager : ExternalAnnotationsManager() {
override fun chooseAnnotationsPlace(element: PsiElement): AnnotationPlace? = null
override fun isExternalAnnotationWritable(listOwner: PsiModifierListOwner, annotationFQN: String): Boolean = false
override fun isExternalAnnotation(annotation: PsiAnnotation): Boolean = false
override fun findExternalAnnotationsFiles(listOwner: PsiModifierListOwner): List<PsiFile>? = null
override fun findExternalAnnotation(listOwner: PsiModifierListOwner, annotationFQN: String): PsiAnnotation? = null
override fun findExternalAnnotations(listOwner: PsiModifierListOwner): Array<out PsiAnnotation>? = null
override fun annotateExternally(
listOwner: PsiModifierListOwner,
annotationFQName: String,
fromFile: PsiFile,
value: Array<out PsiNameValuePair>?
) {
throw UnsupportedOperationException("not implemented")
}
override fun deannotate(listOwner: PsiModifierListOwner, annotationFQN: String): Boolean {
throw UnsupportedOperationException("not implemented")
}
override fun editExternalAnnotation(
listOwner: PsiModifierListOwner,
annotationFQN: String,
value: Array<out PsiNameValuePair>?
): Boolean {
throw UnsupportedOperationException("not implemented")
}
override fun hasAnnotationRootsForFile(file: VirtualFile): Boolean {
throw UnsupportedOperationException("not implemented")
}
override fun findDefaultConstructorExternalAnnotations(aClass: PsiClass, annotationFQN: String): MutableList<PsiAnnotation> {
throw UnsupportedOperationException("not implemented")
}
override fun findDefaultConstructorExternalAnnotations(aClass: PsiClass): MutableList<PsiAnnotation> {
throw UnsupportedOperationException("not implemented")
}
override fun findExternalAnnotations(listOwner: PsiModifierListOwner, annotationFQN: String): MutableList<PsiAnnotation> {
throw UnsupportedOperationException("not implemented")
}
}
@@ -0,0 +1,373 @@
/*
* 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.refactoring.move.moveDeclarations
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.ide.util.EditorHelper
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Ref
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNamedElement
import com.intellij.psi.PsiReference
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.BaseRefactoringProcessor
import com.intellij.refactoring.move.MoveCallback
import com.intellij.refactoring.move.MoveMultipleElementsViewDescriptor
import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassHandler
import com.intellij.refactoring.rename.RenameUtil
import com.intellij.refactoring.util.NonCodeUsageInfo
import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.refactoring.util.TextOccurrencesUtil
import com.intellij.usageView.UsageInfo
import com.intellij.usageView.UsageViewBundle
import com.intellij.usageView.UsageViewDescriptor
import com.intellij.usageView.UsageViewUtil
import com.intellij.util.IncorrectOperationException
import com.intellij.util.containers.MultiMap
import gnu.trove.THashMap
import gnu.trove.TObjectHashingStrategy
import org.jetbrains.kotlin.asJava.elements.KtLightDeclaration
import org.jetbrains.kotlin.asJava.findFacadeClass
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.asJava.toLightElements
import org.jetbrains.kotlin.idea.codeInsight.shorten.addToBeShortenedDescendantsToWaitingSet
import org.jetbrains.kotlin.idea.core.deleteSingle
import org.jetbrains.kotlin.idea.core.quoteIfNeeded
import org.jetbrains.kotlin.idea.refactoring.broadcastRefactoringExit
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.refactoring.move.*
import org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories.MoveKotlinClassHandler
import org.jetbrains.kotlin.idea.search.projectScope
import org.jetbrains.kotlin.idea.search.restrictByFileType
import org.jetbrains.kotlin.idea.util.projectStructure.module
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.utils.ifEmpty
import org.jetbrains.kotlin.utils.keysToMap
import java.lang.AssertionError
import java.util.*
interface Mover : (KtNamedDeclaration, KtElement) -> KtNamedDeclaration {
object Default : Mover {
override fun invoke(originalElement: KtNamedDeclaration, targetContainer: KtElement): KtNamedDeclaration {
return when (targetContainer) {
is KtFile -> {
val declarationContainer: KtElement =
if (targetContainer.isScript()) targetContainer.script!!.blockExpression else targetContainer
declarationContainer.add(originalElement) as KtNamedDeclaration
}
is KtClassOrObject -> targetContainer.addDeclaration(originalElement)
else -> error("Unexpected element: ${targetContainer.getElementTextWithContext()}")
}.apply {
val container = originalElement.containingClassOrObject
if (container is KtObjectDeclaration && container.isCompanion() && container.declarations.singleOrNull() == originalElement) {
container.deleteSingle()
}
else {
originalElement.deleteSingle()
}
}
}
}
}
sealed class MoveSource {
abstract val elementsToMove: Collection<KtNamedDeclaration>
class Elements(override val elementsToMove: Collection<KtNamedDeclaration>) : MoveSource()
class File(val file: KtFile) : MoveSource() {
override val elementsToMove: Collection<KtNamedDeclaration>
get() = file.declarations.filterIsInstance<KtNamedDeclaration>()
}
}
fun MoveSource(declaration: KtNamedDeclaration) = MoveSource.Elements(listOf(declaration))
fun MoveSource(declarations: Collection<KtNamedDeclaration>) = MoveSource.Elements(declarations)
fun MoveSource(file: KtFile) = MoveSource.File(file)
class MoveDeclarationsDescriptor @JvmOverloads constructor(
val project: Project,
val moveSource: MoveSource,
val moveTarget: KotlinMoveTarget,
val delegate: MoveDeclarationsDelegate,
val searchInCommentsAndStrings: Boolean = true,
val searchInNonCode: Boolean = true,
val deleteSourceFiles: Boolean = false,
val moveCallback: MoveCallback? = null,
val openInEditor: Boolean = false,
val allElementsToMove: List<PsiElement>? = null,
val analyzeConflicts: Boolean = true,
val searchReferences: Boolean = true
)
class ConflictUsageInfo(element: PsiElement, val messages: Collection<String>) : UsageInfo(element)
private object ElementHashingStrategy : TObjectHashingStrategy<PsiElement> {
override fun equals(e1: PsiElement?, e2: PsiElement?): Boolean {
if (e1 === e2) return true
// Name should be enough to distinguish different light elements based on the same original declaration
if (e1 is KtLightDeclaration<*, *> && e2 is KtLightDeclaration<*, *>) {
return e1.kotlinOrigin == e2.kotlinOrigin && e1.name == e2.name
}
return e1 == e2
}
override fun computeHashCode(e: PsiElement?): Int {
return when (e) {
null -> 0
is KtLightDeclaration<*, *> -> (e.kotlinOrigin?.hashCode() ?: 0) * 31 + (e.name?.hashCode() ?: 0)
else -> e.hashCode()
}
}
}
class MoveKotlinDeclarationsProcessor(
val descriptor: MoveDeclarationsDescriptor,
val mover: Mover = Mover.Default) : BaseRefactoringProcessor(descriptor.project) {
companion object {
private val REFACTORING_NAME = "Move declarations"
val REFACTORING_ID = "move.kotlin.declarations"
}
val project get() = descriptor.project
private var nonCodeUsages: Array<NonCodeUsageInfo>? = null
private val moveEntireFile = descriptor.moveSource is MoveSource.File
private val elementsToMove = descriptor.moveSource.elementsToMove.filter { e -> e.parent != descriptor.moveTarget.getTargetPsiIfExists(e) }
private val kotlinToLightElementsBySourceFile = elementsToMove
.groupBy { it.containingKtFile }
.mapValues { it.value.keysToMap { it.toLightElements().ifEmpty { listOf(it) } } }
private val conflicts = MultiMap<PsiElement, String>()
override fun getRefactoringId() = REFACTORING_ID
override fun createUsageViewDescriptor(usages: Array<out UsageInfo>): UsageViewDescriptor {
val targetContainerFqName = descriptor.moveTarget.targetContainerFqName?.let {
if (it.isRoot) UsageViewBundle.message("default.package.presentable.name") else it.asString()
}
return MoveMultipleElementsViewDescriptor(elementsToMove.toTypedArray(), targetContainerFqName)
}
fun getConflictsAsUsages(): List<UsageInfo> = conflicts.entrySet().map { ConflictUsageInfo(it.key, it.value) }
public override fun findUsages(): Array<UsageInfo> {
if (!descriptor.searchReferences || elementsToMove.isEmpty()) return UsageInfo.EMPTY_ARRAY
val newContainerName = descriptor.moveTarget.targetContainerFqName?.asString() ?: ""
fun getSearchScope(element: PsiElement): GlobalSearchScope? {
val projectScope = project.projectScope()
val ktDeclaration = element.namedUnwrappedElement as? KtNamedDeclaration ?: return projectScope
if (ktDeclaration.hasModifier(KtTokens.PRIVATE_KEYWORD)) return projectScope
val moveTarget = descriptor.moveTarget
val (oldContainer, newContainer) = descriptor.delegate.getContainerChangeInfo(ktDeclaration, moveTarget)
val targetModule = moveTarget.getTargetModule(project) ?: return projectScope
if (oldContainer != newContainer || ktDeclaration.module != targetModule) return projectScope
// Check if facade class may change
if (newContainer is ContainerInfo.Package) {
val javaScope = projectScope.restrictByFileType(JavaFileType.INSTANCE)
val currentFile = ktDeclaration.containingKtFile
val newFile = when (moveTarget) {
is KotlinMoveTargetForExistingElement -> moveTarget.targetElement as? KtFile ?: return null
is KotlinMoveTargetForDeferredFile -> return javaScope
else -> return null
}
val currentFacade = currentFile.findFacadeClass()
val newFacade = newFile.findFacadeClass()
return if (currentFacade?.qualifiedName != newFacade?.qualifiedName) javaScope else null
}
return null
}
fun collectUsages(kotlinToLightElements: Map<KtNamedDeclaration, List<PsiNamedElement>>, result: MutableCollection<UsageInfo>) {
kotlinToLightElements.values.flatten().flatMapTo(result) { lightElement ->
val searchScope = getSearchScope(lightElement) ?: return@flatMapTo emptyList()
val elementName = lightElement.name ?: return@flatMapTo emptyList()
val newFqName = StringUtil.getQualifiedName(newContainerName, elementName)
val foundReferences = HashSet<PsiReference>()
val results = ReferencesSearch
.search(lightElement, searchScope)
.mapNotNullTo(ArrayList()) { ref ->
if (foundReferences.add(ref) && elementsToMove.none { it.isAncestor(ref.element)}) {
createMoveUsageInfoIfPossible(ref, lightElement, true, false)
}
else null
}
val name = lightElement.getKotlinFqName()?.quoteIfNeeded()?.asString()
if (name != null) {
TextOccurrencesUtil.findNonCodeUsages(
lightElement,
name,
descriptor.searchInCommentsAndStrings,
descriptor.searchInNonCode,
FqName(newFqName).quoteIfNeeded().asString(),
results
)
}
MoveClassHandler.EP_NAME.extensions.forEach { handler ->
if (handler !is MoveKotlinClassHandler) handler.preprocessUsages(results)
}
results
}
}
val usages = ArrayList<UsageInfo>()
val conflictChecker = MoveConflictChecker(
project,
elementsToMove,
descriptor.moveTarget,
elementsToMove.first(),
allElementsToMove = descriptor.allElementsToMove
)
for ((sourceFile, kotlinToLightElements) in kotlinToLightElementsBySourceFile) {
val internalUsages = LinkedHashSet<UsageInfo>()
val externalUsages = LinkedHashSet<UsageInfo>()
if (moveEntireFile) {
val changeInfo = ContainerChangeInfo(
ContainerInfo.Package(sourceFile.packageFqName),
descriptor.moveTarget.targetContainerFqName?.let { ContainerInfo.Package(it) } ?: ContainerInfo.UnknownPackage
)
internalUsages += sourceFile.getInternalReferencesToUpdateOnPackageNameChange(changeInfo)
}
else {
kotlinToLightElements.keys.forEach {
val packageNameInfo = descriptor.delegate.getContainerChangeInfo(it, descriptor.moveTarget)
internalUsages += it.getInternalReferencesToUpdateOnPackageNameChange(packageNameInfo)
}
}
internalUsages += descriptor.delegate.findInternalUsages(descriptor)
collectUsages(kotlinToLightElements, externalUsages)
if (descriptor.analyzeConflicts) {
conflictChecker.checkAllConflicts(externalUsages, internalUsages, conflicts)
descriptor.delegate.collectConflicts(descriptor, internalUsages, conflicts)
}
usages += internalUsages
usages += externalUsages
}
return UsageViewUtil.removeDuplicatedUsages(usages.toTypedArray())
}
override fun preprocessUsages(refUsages: Ref<Array<UsageInfo>>): Boolean {
return showConflicts(conflicts, refUsages.get())
}
override fun performRefactoring(usages: Array<out UsageInfo>) = doPerformRefactoring(usages.toList())
internal fun doPerformRefactoring(usages: List<UsageInfo>) {
fun moveDeclaration(declaration: KtNamedDeclaration, moveTarget: KotlinMoveTarget): KtNamedDeclaration {
val targetContainer = moveTarget.getOrCreateTargetPsi(declaration)
?: throw AssertionError("Couldn't create Kotlin file for: ${declaration::class.java}: ${declaration.text}")
descriptor.delegate.preprocessDeclaration(descriptor, declaration)
if (moveEntireFile) return declaration
return mover(declaration, targetContainer).apply {
addToBeShortenedDescendantsToWaitingSet()
}
}
val (oldInternalUsages, externalUsages) = usages.partition { it is KotlinMoveUsage && it.isInternal }
val newInternalUsages = ArrayList<UsageInfo>()
markInternalUsages(oldInternalUsages)
val usagesToProcess = ArrayList(externalUsages)
try {
descriptor.delegate.preprocessUsages(descriptor, usages)
val oldToNewElementsMapping = THashMap<PsiElement, PsiElement>(ElementHashingStrategy)
val newDeclarations = ArrayList<KtNamedDeclaration>()
for ((sourceFile, kotlinToLightElements) in kotlinToLightElementsBySourceFile) {
for ((oldDeclaration, oldLightElements) in kotlinToLightElements) {
val elementListener = transaction?.getElementListener(oldDeclaration)
val newDeclaration = moveDeclaration(oldDeclaration, descriptor.moveTarget)
newDeclarations += newDeclaration
oldToNewElementsMapping[oldDeclaration] = newDeclaration
oldToNewElementsMapping[sourceFile] = newDeclaration.containingKtFile
elementListener?.elementMoved(newDeclaration)
for ((oldElement, newElement) in oldLightElements.asSequence().zip(newDeclaration.toLightElements().asSequence())) {
oldToNewElementsMapping[oldElement] = newElement
}
if (descriptor.openInEditor) {
EditorHelper.openInEditor(newDeclaration)
}
}
if (descriptor.deleteSourceFiles) {
sourceFile.delete()
}
}
val internalUsageScopes: List<KtElement> = if (moveEntireFile) {
newDeclarations.asSequence().map { it.containingKtFile }.distinct().toList()
} else {
newDeclarations
}
internalUsageScopes.forEach { newInternalUsages += restoreInternalUsages(it, oldToNewElementsMapping) }
usagesToProcess += newInternalUsages
nonCodeUsages = postProcessMoveUsages(usagesToProcess, oldToNewElementsMapping).toTypedArray()
}
catch (e: IncorrectOperationException) {
nonCodeUsages = null
RefactoringUIUtil.processIncorrectOperation(myProject, e)
}
finally {
cleanUpInternalUsages(newInternalUsages + oldInternalUsages)
}
}
override fun performPsiSpoilingRefactoring() {
nonCodeUsages?.let { nonCodeUsages -> RenameUtil.renameNonCodeUsages(myProject, nonCodeUsages) }
descriptor.moveCallback?.refactoringCompleted()
}
fun execute(usages: List<UsageInfo>) {
execute(usages.toTypedArray())
}
override fun doRun() {
try {
super.doRun()
} finally {
broadcastRefactoringExit(myProject, refactoringId)
}
}
override fun getCommandName(): String = REFACTORING_NAME
}
@@ -0,0 +1,122 @@
/*
* Copyright 2010-2015 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.search.ideaExtensions
import com.intellij.codeInsight.JavaTargetElementEvaluator
import com.intellij.codeInsight.TargetElementEvaluatorEx
import com.intellij.codeInsight.TargetElementUtil
import com.intellij.codeInsight.TargetElementUtilExtender
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiReference
import com.intellij.util.BitUtil
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.idea.intentions.isAutoCreatedItUsage
import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.references.resolveMainReferenceToDescriptors
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.resolve.source.getPsi
class KotlinTargetElementEvaluator : TargetElementEvaluatorEx, TargetElementUtilExtender {
companion object {
const val DO_NOT_UNWRAP_LABELED_EXPRESSION = 0x100
const val BYPASS_IMPORT_ALIAS = 0x200
// Place caret after the open curly brace in lambda for generated 'it'
fun findLambdaOpenLBraceForGeneratedIt(ref: PsiReference): PsiElement? {
val element: PsiElement = ref.element
if (element.text != "it") return null
if (element !is KtNameReferenceExpression || !isAutoCreatedItUsage(element)) return null
val itDescriptor = element.resolveMainReferenceToDescriptors().singleOrNull() ?: return null
val descriptorWithSource = itDescriptor.containingDeclaration as? DeclarationDescriptorWithSource ?: return null
val lambdaExpression = descriptorWithSource.source.getPsi()?.parent as? KtLambdaExpression ?: return null
return lambdaExpression.leftCurlyBrace.treeNext?.psi
}
// Navigate to receiver element for this in extension declaration
fun findReceiverForThisInExtensionFunction(ref: PsiReference): PsiElement? {
val element: PsiElement = ref.element
if (element.text != "this") return null
if (element !is KtNameReferenceExpression) return null
val callableDescriptor = element.resolveMainReferenceToDescriptors().singleOrNull() as? CallableDescriptor ?: return null
if (!callableDescriptor.isExtension) return null
val callableDeclaration = callableDescriptor.source.getPsi() as? KtCallableDeclaration ?: return null
return callableDeclaration.receiverTypeReference
}
}
override fun getAdditionalDefinitionSearchFlags() = 0
override fun getAdditionalReferenceSearchFlags() = DO_NOT_UNWRAP_LABELED_EXPRESSION or BYPASS_IMPORT_ALIAS
override fun getAllAdditionalFlags() = additionalDefinitionSearchFlags + additionalReferenceSearchFlags
override fun includeSelfInGotoImplementation(element: PsiElement): Boolean = !(element is KtClass && element.isAbstract())
override fun getElementByReference(ref: PsiReference, flags: Int): PsiElement? {
if (ref is KtSimpleNameReference && ref.expression is KtLabelReferenceExpression) {
val refTarget = ref.resolve() as? KtExpression ?: return null
if (!BitUtil.isSet(flags, DO_NOT_UNWRAP_LABELED_EXPRESSION)) {
return refTarget.getLabeledParent(ref.expression.getReferencedName()) ?: refTarget
}
return refTarget
}
if (!BitUtil.isSet(flags, BYPASS_IMPORT_ALIAS)) {
(ref.element as? KtSimpleNameExpression)?.mainReference?.getImportAlias()?.let { return it }
}
// prefer destructing declaration entry to its target if element name is accepted
if (ref is KtDestructuringDeclarationReference && BitUtil.isSet(flags, TargetElementUtil.ELEMENT_NAME_ACCEPTED)) {
return ref.element
}
val refExpression = ref.element as? KtSimpleNameExpression
val calleeExpression = refExpression?.getParentOfTypeAndBranch<KtCallElement> { calleeExpression }
if (calleeExpression != null) {
(ref.resolve() as? KtConstructor<*>)?.let {
return if (flags and JavaTargetElementEvaluator().additionalReferenceSearchFlags != 0) it else it.containingClassOrObject
}
}
if (BitUtil.isSet(flags, TargetElementUtil.REFERENCED_ELEMENT_ACCEPTED)) {
return findLambdaOpenLBraceForGeneratedIt(ref)
?: findReceiverForThisInExtensionFunction(ref)
}
return null
}
override fun isIdentifierPart(file: PsiFile, text: CharSequence?, offset: Int): Boolean {
val elementAtCaret = file.findElementAt(offset)
if (elementAtCaret?.node?.elementType == KtTokens.IDENTIFIER) return true
// '(' is considered identifier part if it belongs to primary constructor without 'constructor' keyword
return elementAtCaret?.getNonStrictParentOfType<KtPrimaryConstructor>()?.textOffset == offset
}
}
@@ -0,0 +1,118 @@
/*
* Copyright 2010-2017 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.slicer
import com.intellij.codeInspection.dataFlow.Nullness
import com.intellij.ide.util.treeView.AbstractTreeStructure
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.psi.PsiElement
import com.intellij.slicer.*
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.guessTypes
import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isPlainWithEscapes
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.isError
import org.jetbrains.kotlin.types.isNullabilityFlexible
class KotlinSliceProvider : SliceLanguageSupportProvider, SliceUsageTransformer {
companion object {
val LEAF_ELEMENT_EQUALITY = object : SliceLeafEquality() {
override fun substituteElement(element: PsiElement) = (element as? KtReference)?.resolve() ?: element
}
}
class KotlinGroupByNullnessAction(treeBuilder: SliceTreeBuilder) : GroupByNullnessActionBase(treeBuilder) {
override fun isAvailable() = true
}
val leafAnalyzer by lazy { SliceLeafAnalyzer(LEAF_ELEMENT_EQUALITY, this) }
val nullnessAnalyzer: SliceNullnessAnalyzerBase by lazy {
object : SliceNullnessAnalyzerBase(LEAF_ELEMENT_EQUALITY, this) {
override fun checkNullness(element: PsiElement?): Nullness {
val types = when (element) {
is KtCallableDeclaration -> listOfNotNull((element.resolveToDescriptorIfAny() as? CallableDescriptor)?.returnType)
is KtDeclaration -> emptyList()
is KtExpression -> listOfNotNull(element.analyze(BodyResolveMode.PARTIAL).getType(element))
else -> emptyList()
}
return when {
types.isEmpty() -> return Nullness.UNKNOWN
types.all { KotlinBuiltIns.isNullableNothing(it) } -> Nullness.NULLABLE
types.any { it.isError || TypeUtils.isNullableType(it) || it.isNullabilityFlexible() } -> Nullness.UNKNOWN
else -> Nullness.NOT_NULL
}
}
}
}
override fun createRootUsage(element: PsiElement, params: SliceAnalysisParams) = KotlinSliceUsage(element, params)
override fun transform(usage: SliceUsage): Collection<SliceUsage>? {
if (usage is KotlinSliceUsage) return null
return listOf(KotlinSliceUsage(usage.element, usage.parent, 0, false))
}
override fun getExpressionAtCaret(atCaret: PsiElement, dataFlowToThis: Boolean): KtExpression? {
val element =
atCaret.parentsWithSelf
?.firstOrNull {
it is KtProperty ||
it is KtParameter ||
it is KtDeclarationWithBody ||
(it is KtClass && !it.hasExplicitPrimaryConstructor()) ||
(it is KtExpression && it !is KtDeclaration)
}
?.let { KtPsiUtil.safeDeparenthesize(it as KtExpression) } ?: return null
if (dataFlowToThis) {
if (element is KtConstantExpression) return null
if (element is KtStringTemplateExpression && element.isPlainWithEscapes()) return null
if (element is KtClassLiteralExpression) return null
if (element is KtCallableReferenceExpression) return null
}
return element
}
override fun getElementForDescription(element: PsiElement): PsiElement {
return (element as? KtSimpleNameExpression)?.mainReference?.resolve() ?: element
}
override fun getRenderer() = KotlinSliceUsageCellRenderer
override fun startAnalyzeLeafValues(structure: AbstractTreeStructure, finalRunnable: Runnable) {
leafAnalyzer.startAnalyzeValues(structure, finalRunnable)
}
override fun startAnalyzeNullness(structure: AbstractTreeStructure, finalRunnable: Runnable) {
nullnessAnalyzer.startAnalyzeNullness(structure, finalRunnable)
}
override fun registerExtraPanelActions(group: DefaultActionGroup, builder: SliceTreeBuilder) {
if (builder.dataFlowToThis) {
group.add(GroupByLeavesAction(builder))
group.add(KotlinGroupByNullnessAction(builder))
}
}
}
@@ -0,0 +1,57 @@
package org.jetbrains.uast.kotlin.evaluation
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.uast.UBinaryExpression
import org.jetbrains.uast.UastPostfixOperator
import org.jetbrains.uast.evaluation.AbstractEvaluatorExtension
import org.jetbrains.uast.evaluation.UEvaluationInfo
import org.jetbrains.uast.evaluation.UEvaluationState
import org.jetbrains.uast.kotlin.KotlinBinaryOperators
import org.jetbrains.uast.kotlin.KotlinPostfixOperators
import org.jetbrains.uast.values.*
import org.jetbrains.uast.evaluation.to
class KotlinEvaluatorExtension : AbstractEvaluatorExtension(KotlinLanguage.INSTANCE) {
private data class Range(val from: UValue, val to: UValue) {
override fun toString() = "$from..$to"
}
private class UClosedRangeConstant(override val value: Range, override val source: UBinaryExpression?) : UAbstractConstant() {
constructor(from: UValue, to: UValue, source: UBinaryExpression): this(Range(from, to), source)
}
override fun evaluatePostfix(
operator: UastPostfixOperator,
operandValue: UValue,
state: UEvaluationState
): UEvaluationInfo {
return when (operator) {
KotlinPostfixOperators.EXCLEXCL -> when (operandValue.toConstant()) {
UNullConstant -> UValue.UNREACHABLE
is UConstant -> operandValue
else -> UUndeterminedValue
} to state
else -> return super.evaluatePostfix(operator, operandValue, state)
}
}
private fun UValue.contains(value: UValue): UValue {
val range = (this as? UClosedRangeConstant)?.value ?: return UUndeterminedValue
return (value greaterOrEquals range.from) and (value lessOrEquals range.to)
}
override fun evaluateBinary(
binaryExpression: UBinaryExpression,
leftValue: UValue,
rightValue: UValue,
state: UEvaluationState
): UEvaluationInfo {
return when (binaryExpression.operator) {
KotlinBinaryOperators.IN -> rightValue.contains(leftValue)
KotlinBinaryOperators.NOT_IN -> !rightValue.contains(leftValue)
KotlinBinaryOperators.RANGE_TO -> UClosedRangeConstant(leftValue, rightValue, binaryExpression)
else -> UUndeterminedValue
} to state
}
}