mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[kotlin] k2: add a type checker for create expect from actual fix
- disabled case where jvm's HashMap is not mapped to the common HashMap [to be updated] ^KTIJ-33878 fixed GitOrigin-RevId: 9a4a53b0950937679e87c2fffe716816a667c21f
This commit is contained in:
committed by
intellij-monorepo-bot
parent
4ca91861f0
commit
f01400e056
+67
-20
@@ -9,12 +9,14 @@ import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.createSmartPointer
|
||||
import com.intellij.util.containers.addIfNotNull
|
||||
import org.jetbrains.kotlin.analysis.api.KaExperimentalApi
|
||||
import org.jetbrains.kotlin.analysis.api.KaSession
|
||||
import org.jetbrains.kotlin.analysis.api.fir.diagnostics.KaFirDiagnostic
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KaCallableSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KaClassSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KaValueParameterSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.types.KaClassType
|
||||
import org.jetbrains.kotlin.analysis.api.types.KaType
|
||||
import org.jetbrains.kotlin.idea.base.analysis.api.utils.analyzeInModalWindow
|
||||
import org.jetbrains.kotlin.idea.base.analysis.api.utils.shortenReferences
|
||||
@@ -28,6 +30,8 @@ import org.jetbrains.kotlin.idea.core.createFileForDeclaration
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.MemberGenerateMode
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.generateClassWithMembers
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.generateMember
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.introduce.extractionEngine.getUnResolvableInScope
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.introduce.extractionEngine.isResolvableInScope
|
||||
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.CreateClassUtil.getTypeDescription
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint
|
||||
import org.jetbrains.kotlin.idea.refactoring.isInterfaceClass
|
||||
@@ -39,7 +43,6 @@ import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import kotlin.collections.plus
|
||||
|
||||
internal object ActualWithoutExpectFactory {
|
||||
|
||||
@@ -143,7 +146,7 @@ sealed class CreateExpectedFix<D : KtNamedDeclaration>(
|
||||
val targetExpectedClass = targetExpectedClassPointer?.element
|
||||
val expectedFile = targetExpectedClass?.containingKtFile ?: getOrCreateImplementationFile() ?: return
|
||||
val declaration = element ?: return
|
||||
val expectPrototype = generate(project, targetExpectedClass, declaration) ?: return
|
||||
val expectPrototype = generate(project, targetExpectedClass, expectedFile, declaration) ?: return
|
||||
val target = project.executeWriteCommand(familyName, null) {
|
||||
val initial = (when {
|
||||
targetExpectedClass != null && expectPrototype is KtPrimaryConstructor -> targetExpectedClass.add(expectPrototype)
|
||||
@@ -160,7 +163,7 @@ sealed class CreateExpectedFix<D : KtNamedDeclaration>(
|
||||
)
|
||||
}
|
||||
|
||||
abstract fun generate(project: Project, targetExpectedClass: KtClassOrObject?, declaration: D): D?
|
||||
abstract fun generate(project: Project, targetExpectedClass: KtClassOrObject?, expectedFile: KtFile, declaration: D): D?
|
||||
|
||||
fun showInaccessibleDeclarationError(
|
||||
element: PsiElement,
|
||||
@@ -172,38 +175,80 @@ sealed class CreateExpectedFix<D : KtNamedDeclaration>(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(KaExperimentalApi::class)
|
||||
protected fun isCorrectAndHaveAccessibleModifiers(
|
||||
declaration: KtNamedDeclaration,
|
||||
showErrorHint: Boolean = false,
|
||||
expectedFile: KtFile,
|
||||
): Boolean {
|
||||
val inaccessibleModifier = INACCESSIBLE_MODIFIERS.find { declaration.hasModifier(it) }
|
||||
if (inaccessibleModifier != null) {
|
||||
if (showErrorHint) showInaccessibleDeclarationError(
|
||||
showInaccessibleDeclarationError(
|
||||
declaration,
|
||||
KotlinBundle.message("the.declaration.has.0.modifier", inaccessibleModifier)
|
||||
)
|
||||
return false
|
||||
}
|
||||
if (declaration is KtFunction && declaration.hasBody() && declaration.containingClassOrObject?.isInterfaceClass() == true) {
|
||||
if (showErrorHint) showInaccessibleDeclarationError(
|
||||
showInaccessibleDeclarationError(
|
||||
declaration,
|
||||
KotlinBundle.message("the.function.declaration.shouldn.t.have.a.default.implementation")
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
//todo check accessibility of types in the common module
|
||||
//if (!showErrorHint) return checkAccessibility(declaration)
|
||||
//
|
||||
//val types = incorrectTypes(declaration).ifEmpty { return true }
|
||||
//showInaccessibleDeclarationError(
|
||||
// declaration,
|
||||
// KotlinBundle.message(
|
||||
// "some.types.are.not.accessible.from.0.1",
|
||||
// targetModule.name,
|
||||
// TypeAccessibilityChecker.typesToString(types)
|
||||
// )
|
||||
//)
|
||||
val unresolvedTypes = analyzeInModalWindow(declaration, KotlinBundle.message("fix.change.signature.prepare")) {
|
||||
fun MutableList<KaType>.processTypeParametersOwner(owner: KtTypeParameterListOwner) {
|
||||
owner.typeParameters.forEach { addIfNotNull(it.extendsBound?.type) }
|
||||
owner.typeConstraints.forEach { addIfNotNull(it.boundTypeReference?.type) }
|
||||
owner.annotationEntries.forEach { addIfNotNull(it.typeReference?.type) }
|
||||
}
|
||||
|
||||
val usedTypes = when (declaration) {
|
||||
is KtConstructor<*> -> declaration.valueParameters.mapNotNull { it.typeReference?.type }
|
||||
is KtFunction -> buildList {
|
||||
addIfNotNull(declaration.receiverTypeReference?.type)
|
||||
addIfNotNull(declaration.returnType)
|
||||
for (parameter in declaration.valueParameters) {
|
||||
addIfNotNull(parameter.returnType)
|
||||
}
|
||||
processTypeParametersOwner(declaration)
|
||||
}
|
||||
|
||||
is KtProperty -> buildList {
|
||||
addIfNotNull(declaration.receiverTypeReference?.type)
|
||||
addIfNotNull(declaration.returnType)
|
||||
processTypeParametersOwner(declaration)
|
||||
|
||||
}
|
||||
is KtClass -> buildList {
|
||||
if (declaration.isInline()) {
|
||||
declaration.primaryConstructor?.valueParameters?.forEach { addIfNotNull(it.returnType) }
|
||||
}
|
||||
processTypeParametersOwner(declaration)
|
||||
}
|
||||
|
||||
else -> emptyList()
|
||||
}.map { it.abbreviation ?: it }.distinct()
|
||||
|
||||
val expectedVirtualFile = expectedFile.virtualFile
|
||||
usedTypes.mapNotNull {
|
||||
getUnResolvableInScope(it, expectedFile, mutableSetOf()) { classSymbol ->
|
||||
val psi = (classSymbol.getExpectsForActual().firstOrNull() ?: classSymbol).psi
|
||||
val useScope = psi?.useScope
|
||||
psi == declaration || useScope != null && expectedVirtualFile != null && useScope.contains(expectedVirtualFile)
|
||||
}
|
||||
}.mapNotNull { (it as? KaClassType)?.classId?.shortClassName?.asString() }
|
||||
}
|
||||
if (unresolvedTypes.isNotEmpty()) {
|
||||
showInaccessibleDeclarationError(
|
||||
declaration,
|
||||
KotlinBundle.message(
|
||||
"some.types.are.not.accessible.from.0.1",
|
||||
module.name,
|
||||
unresolvedTypes.joinToString()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -224,9 +269,10 @@ internal class CreateExpectedClassFix(
|
||||
override fun generate(
|
||||
project: Project,
|
||||
targetExpectedClass: KtClassOrObject?,
|
||||
expectedFile: KtFile,
|
||||
declaration: KtNamedDeclaration,
|
||||
): KtNamedDeclaration? {
|
||||
if (!isCorrectAndHaveAccessibleModifiers(declaration, true)) return null
|
||||
if (!isCorrectAndHaveAccessibleModifiers(declaration, expectedFile)) return null
|
||||
|
||||
return analyzeInModalWindow(declaration, KotlinBundle.message("fix.change.signature.prepare")) {
|
||||
val classSymbol = declaration.symbol as? KaClassSymbol ?: return@analyzeInModalWindow null
|
||||
@@ -252,9 +298,10 @@ internal class CreateExpectedCallableMemberFix(
|
||||
override fun generate(
|
||||
project: Project,
|
||||
targetExpectedClass: KtClassOrObject?,
|
||||
expectedFile: KtFile,
|
||||
declaration: KtNamedDeclaration,
|
||||
): KtNamedDeclaration? {
|
||||
if (!isCorrectAndHaveAccessibleModifiers(declaration, true)) return null
|
||||
if (!isCorrectAndHaveAccessibleModifiers(declaration, expectedFile)) return null
|
||||
|
||||
return analyzeInModalWindow(declaration, KotlinBundle.message("fix.change.signature.prepare")) {
|
||||
val callableSymbol = declaration.symbol as? KaCallableSymbol ?: return@analyzeInModalWindow null
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
// "Create expected class in common module testModule_Common" "true"
|
||||
// SHOULD_FAIL_WITH: Some types are not accessible from testModule_Common:
|
||||
// SHOULD_FAIL_WITH: JvmAnnotationClass
|
||||
// IGNORE_K2
|
||||
|
||||
|
||||
package one.two
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
// "Create expected class in common module testModule_Common" "true"
|
||||
// SHOULD_FAIL_WITH: Some types are not accessible from testModule_Common:,Some
|
||||
// DISABLE_ERRORS
|
||||
// IGNORE_K2
|
||||
|
||||
|
||||
interface Some
|
||||
|
||||
|
||||
+2
@@ -1,4 +1,6 @@
|
||||
// "Add missing actual declarations" "true"
|
||||
// K2_ACTION: "Create actual in 'testModule_JVM'" "true"
|
||||
// K2_TOOL: org.jetbrains.kotlin.idea.k2.codeinsight.inspections.KotlinNoActualForExpectInspection
|
||||
// SHOULD_FAIL_WITH: Cannot generate class: Type <Unknown> is not accessible from target module
|
||||
// DISABLE_ERRORS
|
||||
// IGNORE_K2
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
// "Create expected function in common module testModule_Common" "true"
|
||||
// SHOULD_FAIL_WITH: Some types are not accessible from testModule_Common:,Some
|
||||
// DISABLE_ERRORS
|
||||
// IGNORE_K2
|
||||
|
||||
class Some
|
||||
|
||||
actual fun <caret>foo(some: Some) {}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
// "Create expected function in common module testModule_Common" "true"
|
||||
// SHOULD_FAIL_WITH: Some types are not accessible from testModule_Common:,Some
|
||||
// DISABLE_ERRORS
|
||||
// IGNORE_K2
|
||||
|
||||
|
||||
interface Some
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
// "Create expected function in common module testModule_Common" "true"
|
||||
// SHOULD_FAIL_WITH: Some types are not accessible from testModule_Common:,Some
|
||||
// DISABLE_ERRORS
|
||||
// IGNORE_K2
|
||||
|
||||
class Some
|
||||
|
||||
actual fun <caret>foo(some: List<Some>) {}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
// "Create expected function in common module testModule_Common" "true"
|
||||
// SHOULD_FAIL_WITH: Some types are not accessible from testModule_Common:,Some
|
||||
// DISABLE_ERRORS
|
||||
// IGNORE_K2
|
||||
|
||||
interface Some
|
||||
|
||||
actual fun <T : Some> <caret>foo(some: List<T>) {}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
// "Create expected function in common module testModule_Common" "true"
|
||||
// SHOULD_FAIL_WITH: Some types are not accessible from testModule_Common:,Some
|
||||
// DISABLE_ERRORS
|
||||
// IGNORE_K2
|
||||
|
||||
interface Some
|
||||
|
||||
actual fun <T : CommonClass, F : Some> <caret>foo(some: List<T>) {}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
// "Create expected property in common module testModule_Common" "true"
|
||||
// SHOULD_FAIL_WITH: Some types are not accessible from testModule_Common:,Some
|
||||
// DISABLE_ERRORS
|
||||
// IGNORE_K2
|
||||
|
||||
interface Some
|
||||
|
||||
actual val foo<caret>: Some = TODO()
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
// "Create expected property in common module testModule_Common" "true"
|
||||
// SHOULD_FAIL_WITH: Some types are not accessible from testModule_Common:,Some
|
||||
// DISABLE_ERRORS
|
||||
// IGNORE_K2
|
||||
|
||||
class Some<T>
|
||||
|
||||
actual val <T>Some<T>.<caret>foo: Some<T> get() = TODO()
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
// "Create expected property in common module testModule_Common" "true"
|
||||
// SHOULD_FAIL_WITH: Some types are not accessible from testModule_Common:,A
|
||||
// DISABLE_ERRORS
|
||||
// IGNORE_K2
|
||||
|
||||
interface A
|
||||
|
||||
actual val <T: A> Some<T>.<caret>foo: Some<T> get() = TODO()
|
||||
-1
@@ -1,7 +1,6 @@
|
||||
// "Create expected function in common module testModule_Common" "true"
|
||||
// SHOULD_FAIL_WITH: Some types are not accessible from testModule_Common:,SomeString
|
||||
// DISABLE_ERRORS
|
||||
// IGNORE_K2
|
||||
|
||||
typealias SomeString = String
|
||||
|
||||
|
||||
-1
@@ -1,7 +1,6 @@
|
||||
// "Create expected function in common module proj_Common" "true"
|
||||
// SHOULD_FAIL_WITH: Some types are not accessible from proj_Common:,ArrayList
|
||||
// DISABLE_ERRORS
|
||||
// IGNORE_K2
|
||||
|
||||
import java.util.ArrayList
|
||||
|
||||
|
||||
-1
@@ -1,7 +1,6 @@
|
||||
// "Create expected function in common module proj_Common" "true"
|
||||
// SHOULD_FAIL_WITH: Some types are not accessible from proj_Common:,ArrayList
|
||||
// DISABLE_ERRORS
|
||||
// IGNORE_K2
|
||||
|
||||
import java.util.ArrayList
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// "Create expected property in common module proj_Common" "true"
|
||||
// DISABLE_ERRORS
|
||||
|
||||
// IGNORE_K2
|
||||
|
||||
actual val some<caret>: HashMap<Int, Int> = TODO()
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// "Create expected property in common module proj_Common" "true"
|
||||
// DISABLE_ERRORS
|
||||
|
||||
// IGNORE_K2
|
||||
|
||||
actual val some: HashMap<Int, Int> = TODO()
|
||||
+48
-14
@@ -3,13 +3,14 @@ package org.jetbrains.kotlin.idea.k2.refactoring.introduce.extractionEngine
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.psi.util.PsiUtil
|
||||
import com.intellij.psi.util.parentOfType
|
||||
import org.jetbrains.kotlin.analysis.api.KaExperimentalApi
|
||||
import org.jetbrains.kotlin.analysis.api.KaSession
|
||||
import org.jetbrains.kotlin.analysis.api.analyze
|
||||
import org.jetbrains.kotlin.analysis.api.renderer.declarations.impl.KaDeclarationRendererForSource
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KaAnonymousObjectSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KaClassSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KaClassLikeSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.typeParameters
|
||||
import org.jetbrains.kotlin.analysis.api.types.*
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.Parameter
|
||||
@@ -121,47 +122,80 @@ class KotlinTypeDescriptor(private val data: IExtractionData) : TypeDescriptor<K
|
||||
*/
|
||||
context(KaSession)
|
||||
@OptIn(KaExperimentalApi::class)
|
||||
fun isResolvableInScope(typeToCheck: KaType, scope: PsiElement, typeParameters: MutableSet<TypeParameter>): Boolean {
|
||||
fun isResolvableInScope(
|
||||
typeToCheck: KaType,
|
||||
scope: PsiElement,
|
||||
typeParameters: MutableSet<TypeParameter>,
|
||||
): Boolean {
|
||||
return getUnResolvableInScope(typeToCheck, scope, typeParameters) == null
|
||||
}
|
||||
|
||||
context(KaSession)
|
||||
@OptIn(KaExperimentalApi::class)
|
||||
fun getUnResolvableInScope(
|
||||
typeToCheck: KaType,
|
||||
scope: PsiElement,
|
||||
typeParameters: MutableSet<TypeParameter>,
|
||||
classAccessibilityChecker: (KaClassLikeSymbol) -> Boolean = { true }
|
||||
): KaType? {
|
||||
require(scope.containingFile is KtFile)
|
||||
((typeToCheck as? KaTypeParameterType)?.symbol?.psi as? KtTypeParameter)?.let { typeParameter ->
|
||||
val typeParameterListOwner = typeParameter.parentOfType<KtTypeParameterListOwner>()
|
||||
if (typeParameterListOwner == null || !PsiTreeUtil.isAncestor(typeParameterListOwner, scope, true)) {
|
||||
typeParameters.add(TypeParameter(typeParameter, typeParameter.collectRelevantConstraints()))
|
||||
}
|
||||
return true
|
||||
return null
|
||||
}
|
||||
if (typeToCheck is KaClassType) {
|
||||
|
||||
val classSymbol = typeToCheck.symbol
|
||||
if ((classSymbol as? KaAnonymousObjectSymbol)?.superTypes?.all { isResolvableInScope(it, scope, typeParameters) } == true) {
|
||||
return true
|
||||
val unresolvedInSuperType = (classSymbol as? KaAnonymousObjectSymbol)?.superTypes?.firstNotNullOfOrNull {
|
||||
getUnResolvableInScope(
|
||||
it,
|
||||
scope,
|
||||
typeParameters,
|
||||
classAccessibilityChecker
|
||||
)
|
||||
}
|
||||
if (unresolvedInSuperType != null) {
|
||||
return unresolvedInSuperType
|
||||
}
|
||||
|
||||
if ((classSymbol as? KaClassSymbol)?.classId == null) {
|
||||
if (classSymbol.classId == null) {
|
||||
//because org.jetbrains.kotlin.fir.FirVisibilityChecker.Default always return true for local classes,
|
||||
//let's be pessimistic here and prohibit local classes completely
|
||||
return false
|
||||
return typeToCheck
|
||||
}
|
||||
|
||||
if (!classAccessibilityChecker(classSymbol)) {
|
||||
return typeToCheck
|
||||
}
|
||||
|
||||
val fileSymbol = (scope.containingFile as KtFile).symbol
|
||||
if (!createUseSiteVisibilityChecker(fileSymbol, receiverExpression = null, scope).isVisible(classSymbol)) {
|
||||
return false
|
||||
return typeToCheck
|
||||
}
|
||||
|
||||
typeToCheck.typeArguments.mapNotNull { it.type }.forEach {
|
||||
if (!isResolvableInScope(it, scope, typeParameters)) return false
|
||||
val unresolvedInTypeArguments = typeToCheck.typeArguments.mapNotNull { it.type }.firstNotNullOfOrNull {
|
||||
getUnResolvableInScope(it, scope, typeParameters, classAccessibilityChecker)
|
||||
}
|
||||
if (unresolvedInTypeArguments != null) {
|
||||
return unresolvedInTypeArguments
|
||||
}
|
||||
}
|
||||
if (typeToCheck is KaErrorType) {
|
||||
return false
|
||||
return typeToCheck
|
||||
}
|
||||
if (typeToCheck is KaIntersectionType) {
|
||||
return false
|
||||
return typeToCheck
|
||||
}
|
||||
if (typeToCheck is KaDefinitelyNotNullType) {
|
||||
if (!isResolvableInScope(typeToCheck.original, scope, typeParameters)) return false
|
||||
val unresolvedOriginal = getUnResolvableInScope(typeToCheck.original, scope, typeParameters, classAccessibilityChecker)
|
||||
if (unresolvedOriginal != null) {
|
||||
return unresolvedOriginal
|
||||
}
|
||||
}
|
||||
return true
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user