diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/KtClassOrObject.kt.181 b/compiler/psi/src/org/jetbrains/kotlin/psi/KtClassOrObject.kt.181 new file mode 100644 index 00000000000..cac16b5323f --- /dev/null +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/KtClassOrObject.kt.181 @@ -0,0 +1,142 @@ +/* + * 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.psi + +import com.intellij.lang.ASTNode +import com.intellij.navigation.ItemPresentation +import com.intellij.navigation.ItemPresentationProviders +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.impl.CheckUtil +import com.intellij.psi.stubs.IStubElementType +import com.intellij.psi.tree.TokenSet +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.stubs.KotlinClassOrObjectStub +import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes + +abstract class KtClassOrObject : + KtTypeParameterListOwnerStub>, KtDeclarationContainer, KtNamedDeclaration, + KtPureClassOrObject { + constructor(node: ASTNode) : super(node) + constructor(stub: KotlinClassOrObjectStub, nodeType: IStubElementType<*, *>) : super(stub, nodeType) + + fun getSuperTypeList(): KtSuperTypeList? = getStubOrPsiChild(KtStubElementTypes.SUPER_TYPE_LIST) + + override fun getSuperTypeListEntries(): List = getSuperTypeList()?.entries.orEmpty() + + fun addSuperTypeListEntry(superTypeListEntry: KtSuperTypeListEntry): KtSuperTypeListEntry { + getSuperTypeList()?.let { + val single = it.entries.singleOrNull() + if (single != null && single.typeReference?.typeElement == null) { + return single.replace(superTypeListEntry) as KtSuperTypeListEntry + } + return EditCommaSeparatedListHelper.addItem(it, superTypeListEntries, superTypeListEntry) + } + + val psiFactory = KtPsiFactory(this) + val specifierListToAdd = psiFactory.createSuperTypeCallEntry("A()").replace(superTypeListEntry).parent + val colon = addBefore(psiFactory.createColon(), getBody()) + return (addAfter(specifierListToAdd, colon) as KtSuperTypeList).entries.first() + } + + fun removeSuperTypeListEntry(superTypeListEntry: KtSuperTypeListEntry) { + val specifierList = getSuperTypeList() ?: return + assert(superTypeListEntry.parent === specifierList) + + if (specifierList.entries.size > 1) { + EditCommaSeparatedListHelper.removeItem(superTypeListEntry) + } else { + deleteChildRange(findChildByType(KtTokens.COLON) ?: specifierList, specifierList) + } + } + + fun getAnonymousInitializers(): List = getBody()?.anonymousInitializers.orEmpty() + + fun getBody(): KtClassBody? = getStubOrPsiChild(KtStubElementTypes.CLASS_BODY) + + inline fun addDeclaration(declaration: T): T { + val body = getOrCreateBody() + val anchor = PsiTreeUtil.skipSiblingsBackward(body.rBrace ?: body.lastChild!!, PsiWhiteSpace::class.java) + return body.addAfter(declaration, anchor) as T + } + + inline fun addDeclarationAfter(declaration: T, anchor: PsiElement?): T { + val anchorBefore = anchor ?: declarations.lastOrNull() ?: return addDeclaration(declaration) + return getOrCreateBody().addAfter(declaration, anchorBefore) as T + } + + inline fun addDeclarationBefore(declaration: T, anchor: PsiElement?): T { + val anchorAfter = anchor ?: declarations.firstOrNull() ?: return addDeclaration(declaration) + return getOrCreateBody().addBefore(declaration, anchorAfter) as T + } + + fun isTopLevel(): Boolean = stub?.isTopLevel() ?: (parent is KtFile) + + override fun isLocal(): Boolean = stub?.isLocal() ?: KtPsiUtil.isLocal(this) + + override fun getDeclarations(): List = getBody()?.declarations.orEmpty() + + override fun getPresentation(): ItemPresentation? = ItemPresentationProviders.getItemPresentation(this) + + override fun getPrimaryConstructor(): KtPrimaryConstructor? = getStubOrPsiChild(KtStubElementTypes.PRIMARY_CONSTRUCTOR) + + override fun getPrimaryConstructorModifierList(): KtModifierList? = primaryConstructor?.modifierList + + fun getPrimaryConstructorParameterList(): KtParameterList? = primaryConstructor?.valueParameterList + + override fun getPrimaryConstructorParameters(): List = getPrimaryConstructorParameterList()?.parameters.orEmpty() + + override fun hasExplicitPrimaryConstructor(): Boolean = primaryConstructor != null + + override fun hasPrimaryConstructor(): Boolean = hasExplicitPrimaryConstructor() || !hasSecondaryConstructors() + + private fun hasSecondaryConstructors(): Boolean = !secondaryConstructors.isEmpty() + + override fun getSecondaryConstructors(): List = getBody()?.secondaryConstructors.orEmpty() + + fun isAnnotation(): Boolean = hasModifier(KtTokens.ANNOTATION_KEYWORD) + + fun getDeclarationKeyword(): PsiElement? = + findChildByType( + TokenSet.create( + KtTokens.CLASS_KEYWORD, KtTokens.INTERFACE_KEYWORD, KtTokens.OBJECT_KEYWORD + ) + ) + + override fun delete() { + CheckUtil.checkWritable(this) + + val file = containingKtFile + if (!isTopLevel() || file.declarations.size > 1) { + super.delete() + } else { + file.delete() + } + } +} + +fun KtClassOrObject.getOrCreateBody(): KtClassBody { + getBody()?.let { return it } + + val newBody = KtPsiFactory(this).createEmptyClassBody() + if (this is KtEnumEntry) return addAfter(newBody, initializerList ?: nameIdentifier) as KtClassBody + return add(newBody) as KtClassBody +} + +val KtClassOrObject.allConstructors + get() = listOfNotNull(primaryConstructor) + secondaryConstructors \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt.181 b/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt.181 new file mode 100644 index 00000000000..a0858d1a177 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt.181 @@ -0,0 +1,460 @@ +/* + * 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.refactoring.safeDelete + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.Messages +import com.intellij.openapi.util.Condition +import com.intellij.openapi.util.Conditions +import com.intellij.openapi.util.Key +import com.intellij.psi.* +import com.intellij.psi.search.searches.ReferencesSearch +import com.intellij.refactoring.RefactoringBundle +import com.intellij.refactoring.safeDelete.JavaSafeDeleteProcessor +import com.intellij.refactoring.safeDelete.NonCodeUsageSearchInfo +import com.intellij.refactoring.safeDelete.usageInfo.SafeDeleteOverrideAnnotation +import com.intellij.refactoring.safeDelete.usageInfo.SafeDeleteOverridingMethodUsageInfo +import com.intellij.refactoring.safeDelete.usageInfo.SafeDeleteReferenceJavaDeleteUsageInfo +import com.intellij.refactoring.safeDelete.usageInfo.SafeDeleteReferenceSimpleDeleteUsageInfo +import com.intellij.usageView.UsageInfo +import org.jetbrains.annotations.TestOnly +import org.jetbrains.kotlin.asJava.* +import org.jetbrains.kotlin.asJava.elements.KtLightField +import org.jetbrains.kotlin.asJava.elements.KtLightFieldImpl +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall +import org.jetbrains.kotlin.idea.core.deleteElementAndCleanParent +import org.jetbrains.kotlin.idea.refactoring.checkSuperMethods +import org.jetbrains.kotlin.idea.refactoring.formatClass +import org.jetbrains.kotlin.idea.refactoring.formatFunction +import org.jetbrains.kotlin.idea.refactoring.withExpectedActuals +import org.jetbrains.kotlin.idea.references.KtReference +import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions +import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchParameters +import org.jetbrains.kotlin.idea.search.usagesSearch.constructor +import org.jetbrains.kotlin.idea.search.usagesSearch.processDelegationCallConstructorUsages +import org.jetbrains.kotlin.idea.util.actualsForExpected +import org.jetbrains.kotlin.idea.util.liftToExpected +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch +import org.jetbrains.kotlin.psi.psiUtil.parameterIndex +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.utils.SmartSet +import org.jetbrains.kotlin.utils.ifEmpty +import java.util.* + +class KotlinSafeDeleteProcessor : JavaSafeDeleteProcessor() { + companion object { + @set:TestOnly + internal var Project.ALLOW_LIFTING_ACTUAL_PARAMETER_TO_EXPECTED + by NotNullableUserDataProperty(Key.create("ALLOW_LIFTING_ACTUAL_PARAMETER_TO_EXPECTED"), true) + + private var KtDeclaration.dropActualModifier: Boolean? by UserDataProperty(Key.create("DROP_ACTUAL_MODIFIER")) + } + + override fun handlesElement(element: PsiElement): Boolean = element.canDeleteElement() + + override fun findUsages( + element: PsiElement, allElementsToDelete: Array, usages: MutableList + ): NonCodeUsageSearchInfo { + val deleteSet = SmartSet.create() + deleteSet.addAll(allElementsToDelete) + + fun getIgnoranceCondition() = Condition { + if (it is KtFile) return@Condition false + deleteSet.any { element -> JavaSafeDeleteProcessor.isInside(it, element.unwrapped) } + } + + fun getSearchInfo(element: PsiElement) = NonCodeUsageSearchInfo(getIgnoranceCondition(), element) + + fun searchKotlinDeclarationReferences(declaration: KtDeclaration): Sequence { + val elementsToSearch = if (declaration is KtParameter) declaration.withExpectedActuals() else listOf(declaration) + return elementsToSearch.asSequence().flatMap { + val searchParameters = KotlinReferencesSearchParameters( + it, + it.useScope, + kotlinOptions = KotlinReferencesSearchOptions(acceptCallableOverrides = true) + ) + ReferencesSearch.search(searchParameters) + .asSequence() + .filterNot { reference -> getIgnoranceCondition().value(reference.element) } + } + } + + fun findKotlinParameterUsages(parameter: KtParameter) { + val ownerFunction = parameter.ownerFunction as? KtFunction ?: return + val index = parameter.parameterIndex() + for (reference in searchKotlinDeclarationReferences(ownerFunction)) { + val callee = reference.element as? KtExpression ?: continue + val resolvedCall = callee.resolveToCall(BodyResolveMode.FULL) ?: continue + val parameterDescriptor = resolvedCall.candidateDescriptor.valueParameters.getOrNull(index) ?: continue + val resolvedArgument = resolvedCall.valueArguments[parameterDescriptor] ?: continue + val arguments = resolvedArgument.arguments.filterIsInstance() + if (arguments.isEmpty()) continue + + usages.add(SafeDeleteValueArgumentListUsageInfo(parameter, *arguments.toTypedArray())) + } + } + + fun findKotlinDeclarationUsages(declaration: KtDeclaration): NonCodeUsageSearchInfo { + searchKotlinDeclarationReferences(declaration).mapNotNullTo(usages) { reference -> + val refElement = reference.element ?: return@mapNotNullTo null + refElement.getNonStrictParentOfType()?.let { importDirective -> + SafeDeleteImportDirectiveUsageInfo(importDirective, element) + } ?: SafeDeleteReferenceSimpleDeleteUsageInfo(refElement, declaration, false) + } + + if (declaration is KtParameter) { + findKotlinParameterUsages(declaration) + } + + return getSearchInfo(declaration) + } + + fun asLightElements(ktElements: Array) = + ktElements.flatMap { (it as? KtElement)?.toLightElements() ?: listOf(it) }.toTypedArray() + + fun findUsagesByJavaProcessor(element: PsiElement, forceReferencedElementUnwrapping: Boolean): NonCodeUsageSearchInfo? { + val javaUsages = ArrayList() + + val elementToPassToJava = when (element) { + is KtLightFieldImpl<*> -> object : KtLightField by element { + // Suppress walking through initializer compiled PSI (it doesn't contain any reference expressions anyway) + override fun getInitializer() = null + } + else -> element + } + val searchInfo = super.findUsages(elementToPassToJava, asLightElements(allElementsToDelete), javaUsages) + + javaUsages.filterIsInstance().mapNotNullTo(deleteSet) { it.element } + + val ignoranceCondition = getIgnoranceCondition() + + javaUsages.mapNotNullTo(usages) { usageInfo -> + when (usageInfo) { + is SafeDeleteOverridingMethodUsageInfo -> + usageInfo.smartPointer.element?.let { usageElement -> + KotlinSafeDeleteOverridingUsageInfo(usageElement, usageInfo.referencedElement) + } + + is SafeDeleteOverrideAnnotation -> + usageInfo.smartPointer.element?.let { usageElement -> + if (usageElement.toLightMethods().all { method -> method.findSuperMethods().isEmpty() }) { + KotlinSafeDeleteOverrideAnnotation(usageElement, usageInfo.referencedElement) + } + else null + } + + is SafeDeleteReferenceJavaDeleteUsageInfo -> + usageInfo.element?.let { usageElement -> + when { + usageElement.getNonStrictParentOfType() != null -> null + ignoranceCondition.value(usageElement) -> null + else -> { + usageElement.getNonStrictParentOfType()?.let { importDirective -> + SafeDeleteImportDirectiveUsageInfo(importDirective, element) + } ?: usageElement.getParentOfTypeAndBranch { typeReference }?.let { + if (element is PsiClass && element.isInterface) SafeDeleteSuperTypeUsageInfo(it, element) else usageInfo + } ?: if (forceReferencedElementUnwrapping) { + SafeDeleteReferenceJavaDeleteUsageInfo(usageElement, element.unwrapped, usageInfo.isSafeDelete) + } else usageInfo + } + } + } + + else -> usageInfo + } + } + + return searchInfo + } + + fun findUsagesByJavaProcessor(elements: Sequence, insideDeleted: Condition): Condition = + elements + .mapNotNull { element -> findUsagesByJavaProcessor(element, true)?.insideDeletedCondition } + .fold(insideDeleted) { condition1, condition2 -> Conditions.or(condition1, condition2) } + + fun findUsagesByJavaProcessor(ktDeclaration: KtDeclaration): NonCodeUsageSearchInfo { + val lightElements = ktDeclaration.toLightElements() + if (lightElements.isEmpty()) { + return findKotlinDeclarationUsages(ktDeclaration) + } + return NonCodeUsageSearchInfo( + findUsagesByJavaProcessor( + lightElements.asSequence(), + getIgnoranceCondition() + ), + ktDeclaration + ) + } + + fun findTypeParameterUsages(parameter: KtTypeParameter) { + val owner = parameter.getNonStrictParentOfType() ?: return + + val parameterList = owner.typeParameters + val parameterIndex = parameterList.indexOf(parameter) + + for (reference in ReferencesSearch.search(owner)) { + if (reference !is KtReference) continue + + val referencedElement = reference.element + + val argList = referencedElement.getNonStrictParentOfType()?.typeArgumentList + ?: referencedElement.getNonStrictParentOfType()?.typeArgumentList + + if (argList != null) { + val projections = argList.arguments + if (parameterIndex < projections.size) { + usages.add(SafeDeleteTypeArgumentListUsageInfo(projections[parameterIndex], parameter)) + } + } + } + } + + fun findDelegationCallUsages(element: PsiElement) { + val constructors = when (element) { + is KtClass -> element.allConstructors + is PsiClass -> element.constructors.toList() + is PsiMethod -> listOf(element) + else -> return + } + for (constructor in constructors) { + constructor.processDelegationCallConstructorUsages(constructor.useScope) { + if (!getIgnoranceCondition().value(it)) { + usages.add(SafeDeleteReferenceSimpleDeleteUsageInfo(it, element, false)) + } + true + } + } + } + + return when (element) { + is KtClassOrObject -> { + findDelegationCallUsages(element) + findKotlinDeclarationUsages(element) + } + + is KtSecondaryConstructor -> { + element.getRepresentativeLightMethod()?.let { method -> + findDelegationCallUsages(method) + findUsagesByJavaProcessor(method, false) + } ?: findKotlinDeclarationUsages(element) + } + + is KtNamedFunction -> { + if (element.isLocal) { + findKotlinDeclarationUsages(element) + } + else { + val lightMethods = element.toLightMethods() + if (lightMethods.isNotEmpty()) { + lightMethods.map { method -> findUsagesByJavaProcessor(method, false) }.firstOrNull() + } + else { + findKotlinDeclarationUsages(element) + } + } + } + + is PsiMethod -> { + findUsagesByJavaProcessor(element, false) + } + + is PsiClass -> { + findUsagesByJavaProcessor(element, false) + } + + is KtProperty -> { + if (element.isLocal) { + findKotlinDeclarationUsages(element) + } + else { + findUsagesByJavaProcessor(element) + } + } + + is KtTypeParameter -> { + findTypeParameterUsages(element) + findUsagesByJavaProcessor(element) + } + + is KtParameter -> + findUsagesByJavaProcessor(element) + + is KtTypeAlias -> { + findKotlinDeclarationUsages(element) + } + + else -> null + } ?: getSearchInfo(element) + } + + override fun findConflicts(element: PsiElement, allElementsToDelete: Array): MutableCollection? { + if (element is KtNamedFunction || element is KtProperty) { + val jetClass = element.getNonStrictParentOfType() + if (jetClass == null || jetClass.getBody() != element.parent) return null + + val modifierList = jetClass.modifierList + if (modifierList != null && modifierList.hasModifier(KtTokens.ABSTRACT_KEYWORD)) return null + + val bindingContext = (element as KtElement).analyze() + + val declarationDescriptor = + bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, element] as? CallableMemberDescriptor ?: return null + + return declarationDescriptor.overriddenDescriptors + .asSequence() + .filter { overridenDescriptor -> overridenDescriptor.modality == Modality.ABSTRACT } + .mapTo(ArrayList()) { overridenDescriptor -> + KotlinBundle.message( + "x.implements.y", + formatFunction(declarationDescriptor, true), + formatClass(declarationDescriptor.containingDeclaration, true), + formatFunction(overridenDescriptor, true), + formatClass(overridenDescriptor.containingDeclaration, true) + ) + } + } + + return super.findConflicts(element, allElementsToDelete) + } + + /* + * Mostly copied from JavaSafeDeleteProcessor.preprocessUsages + * Revision: d4fc033 + * (replaced original dialog) + */ + override fun preprocessUsages(project: Project, usages: Array): Array? { + val result = ArrayList() + val overridingMethodUsages = ArrayList() + + for (usage in usages) { + if (usage is KotlinSafeDeleteOverridingUsageInfo) { + overridingMethodUsages.add(usage) + } else { + result.add(usage) + } + } + + if (!overridingMethodUsages.isEmpty()) { + if (ApplicationManager.getApplication()!!.isUnitTestMode) { + result.addAll(overridingMethodUsages) + } else { + val dialog = KotlinOverridingDialog(project, overridingMethodUsages) + dialog.show() + + if (!dialog.isOK) return null + + result.addAll(dialog.selected) + } + } + + return result.toTypedArray() + } + + override fun prepareForDeletion(element: PsiElement) { + if (element is KtDeclaration) { + element.actualsForExpected().forEach { + if (it is KtParameter) { + (it.parent as? KtParameterList)?.removeParameter(it) + } + else { + it.removeModifier(KtTokens.IMPL_KEYWORD) + it.removeModifier(KtTokens.ACTUAL_KEYWORD) + } + } + } + + when (element) { + is PsiMethod -> element.cleanUpOverrides() + + is KtNamedFunction -> + if (!element.isLocal) { + element.getRepresentativeLightMethod()?.cleanUpOverrides() + } + + is KtProperty -> + if (!element.isLocal) { + element.toLightMethods().forEach(PsiMethod::cleanUpOverrides) + } + + is KtTypeParameter -> + element.deleteElementAndCleanParent() + + is KtParameter -> { + element.ownerFunction?.let { + if (it.dropActualModifier == true) { + it.removeModifier(KtTokens.IMPL_KEYWORD) + it.removeModifier(KtTokens.ACTUAL_KEYWORD) + it.dropActualModifier = null + } + } + (element.parent as KtParameterList).removeParameter(element) + } + } + } + + private fun shouldAllowPropagationToExpected(parameter: KtParameter): Boolean { + if (ApplicationManager.getApplication().isUnitTestMode) return parameter.project.ALLOW_LIFTING_ACTUAL_PARAMETER_TO_EXPECTED + + return Messages.showYesNoDialog( + "Do you want to delete this parameter in expected declaration and all related actual ones?", + RefactoringBundle.message("safe.delete.title"), + Messages.getQuestionIcon() + ) == Messages.YES + } + + override fun getElementsToSearch( + element: PsiElement, module: Module?, allElementsToDelete: Collection + ): Collection? { + when (element) { + is KtParameter -> { + val expectParameter = element.liftToExpected() as? KtParameter + if (expectParameter != null && expectParameter != element) { + if (shouldAllowPropagationToExpected(element)) { + return listOf(expectParameter) + } else { + element.ownerFunction?.dropActualModifier = true + return listOf(element) + } + } + + return element.toPsiParameters().flatMap { psiParameter -> + checkParametersInMethodHierarchy(psiParameter) ?: emptyList() + }.ifEmpty { listOf(element) } + } + + is PsiParameter -> + return checkParametersInMethodHierarchy(element) + } + + if (ApplicationManager.getApplication()!!.isUnitTestMode) return Collections.singletonList(element) + + return when (element) { + is KtNamedFunction, is KtProperty -> checkSuperMethods(element as KtDeclaration, allElementsToDelete, "delete (with usage search)") + else -> super.getElementsToSearch(element, module, allElementsToDelete) + } + } +} diff --git a/idea/testData/refactoring/safeDelete/deleteClass/kotlinClass/trait2.kt.messages.181 b/idea/testData/refactoring/safeDelete/deleteClass/kotlinClass/trait2.kt.messages.181 new file mode 100644 index 00000000000..fbc0e43ed1c --- /dev/null +++ b/idea/testData/refactoring/safeDelete/deleteClass/kotlinClass/trait2.kt.messages.181 @@ -0,0 +1 @@ +interface test.A has 1 usage that is not safe to delete. \ No newline at end of file