do not perform double conversion KtType<->JvmType, to avoid losing some type info in tricky cases (part of KTIJ-28926 K2: Create Function From Usage Fix)

GitOrigin-RevId: 1c86e6c4ef2c3ba3dbcff8caca906eac57d083aa
This commit is contained in:
Alexey Kudravtsev
2024-03-28 13:15:12 +01:00
committed by intellij-monorepo-bot
parent 9b68f11396
commit bd069c730f
9 changed files with 112 additions and 59 deletions

View File

@@ -8,7 +8,7 @@ import com.intellij.psi.PsiTypeVisitor
import com.intellij.psi.PsiTypes
import com.intellij.psi.search.GlobalSearchScope
class ExpectedTypeWithNullability(val type: JvmType, val nullability: Nullability) : ExpectedType {
open class ExpectedTypeWithNullability(val type: JvmType, val nullability: Nullability) : ExpectedType {
override fun getTheType(): JvmType = type
override fun getTheKind(): ExpectedType.Kind = ExpectedType.Kind.EXACT

View File

@@ -82,10 +82,10 @@ object CreateFromUsageUtil {
val declarationInPlace = when {
declaration is KtPrimaryConstructor -> {
(container as KtClass).createPrimaryConstructorIfAbsent().replaced(declaration)
(container as KtClass).createPrimaryConstructorIfAbsent().replaced(declaration)
}
declaration is KtProperty && container !is KtBlockExpression -> {
declaration is KtProperty && container !is KtBlockExpression -> {
val sibling = actualContainer.getChildOfType<KtProperty>() ?: when (actualContainer) {
is KtClassBody -> actualContainer.declarations.firstOrNull() ?: actualContainer.rBrace
is KtFile -> actualContainer.declarations.first()
@@ -126,7 +126,7 @@ object CreateFromUsageUtil {
sibling = container.body?.lBrace
}
org.jetbrains.kotlin.idea.core.insertMembersAfterAndReformat(null, container, declaration, sibling)
org.jetbrains.kotlin.idea.core.insertMembersAfterAndReformat(null, container, declaration, sibling)
}
else -> throw KotlinExceptionWithAttachments("Invalid containing element: ${container::class.java}")
.withPsiAttachment("container", container)

View File

@@ -13,6 +13,7 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiNameHelper
import com.intellij.psi.SmartPsiElementPointer
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.analysis.api.types.KtTypeParameterType
@@ -38,16 +39,16 @@ internal class CreateKotlinCallableAction(
private val myText: String,
pointerToContainer: SmartPsiElementPointer<*>,
) : CreateKotlinElementAction(request, pointerToContainer), JvmGroupIntentionAction {
data class ParamCandidate(val names: Collection<String>, val renderedTypes: List<String>)
private val parameterCandidates: List<ParamCandidate> = renderCandidatesOfParameterTypes()
private val candidatesOfRenderedReturnType: List<String> = renderCandidatesOfReturnType()
private val containerClassFqName: FqName? = (getContainer() as? KtClassOrObject)?.fqName
private val call: PsiElement? = when (request) {
is CreateMethodFromKotlinUsageRequest -> request.call
is CreateExecutableFromJavaUsageRequest<*> -> request.call
else -> null
}
data class ParamCandidate(val names: Collection<String>, val renderedTypes: List<String>)
private val parameterCandidates: List<ParamCandidate> = renderCandidatesOfParameterTypes()
private val candidatesOfRenderedReturnType: List<String> = renderCandidatesOfReturnType()
private val containerClassFqName: FqName? = (getContainer() as? KtClassOrObject)?.fqName
private val isForCompanion: Boolean = (request as? CreateMethodFromKotlinUsageRequest)?.isForCompanion == true
// Note that this property must be initialized after initializing above properties, because it has dependency on them.
@@ -96,19 +97,12 @@ internal class CreateKotlinCallableAction(
}
private fun renderCandidatesOfParameterTypes(): List<ParamCandidate> {
request.expectedParameters.map { it.semanticNames }
val container = getContainer()
return request.expectedParameters.map { expectedParameter ->
val types = if (container == null) listOf("Any") else
analyze(container) {
analyze(call as? KtElement ?: container) {
expectedParameter.expectedTypes.map {
val parameterType =
if (it is ExpectedTypeWithNullability) {
toKtTypeWithNullability(it, container)
} else {
it.theType.toKtType(container)
}
parameterType?.render(renderer = WITH_TYPE_NAMES_FOR_CREATE_ELEMENTS, position = Variance.INVARIANT) ?: "Any"
renderTypeName(it, container) ?: "Any"
}
}
ParamCandidate(expectedParameter.semanticNames, types)
@@ -117,17 +111,24 @@ internal class CreateKotlinCallableAction(
private fun renderCandidatesOfReturnType(): List<String> {
val container = getContainer() ?: return emptyList()
return analyze(container) {
return analyze(call as? KtElement ?: container) {
request.returnType.mapNotNull { returnType ->
val returnKtType = if (returnType is ExpectedTypeWithNullability) toKtTypeWithNullability(returnType, container) else returnType.theType.toKtType(container)
if (returnKtType == null || returnKtType == builtinTypes.UNIT) null
else returnKtType.render(renderer = WITH_TYPE_NAMES_FOR_CREATE_ELEMENTS, position = Variance.INVARIANT)
renderTypeName(returnType, container)
}
}
}
context (KtAnalysisSession)
private fun renderTypeName(expectedType: ExpectedType, container: KtElement): String? {
val returnKtType = if (expectedType is ExpectedKotlinType) expectedType.ktType else expectedType.toKtTypeWithNullability(container)
if (returnKtType == null || returnKtType == builtinTypes.UNIT) return null
if (!isAccessibleInCreationPlace(returnKtType, (request as? CreateMethodFromKotlinUsageRequest)?.call)) return null
return returnKtType.render(renderer = WITH_TYPE_NAMES_FOR_CREATE_ELEMENTS, position = Variance.INVARIANT)
}
private fun buildCallableAsString(): String? {
if (call == null || getContainer() == null) return null
val container = getContainer()
if (call == null || container == null) return null
val modifierListAsString =
request.modifiers.filter{it != JvmModifier.PUBLIC}.joinToString(
separator = " ",
@@ -143,7 +144,7 @@ internal class CreateKotlinCallableAction(
append(" ")
val (receiver, receiverTypeText) = if (request is CreateMethodFromKotlinUsageRequest) CreateKotlinCallableActionTextBuilder.renderReceiver(request) else "" to ""
append(renderTypeParameterDeclarations(request, receiver, receiverTypeText))
append(renderTypeParameterDeclarations(request, container, receiver, receiverTypeText))
append(request.methodName)
append("(")
append(renderParameterList())
@@ -153,10 +154,15 @@ internal class CreateKotlinCallableAction(
}
}
private fun renderTypeParameterDeclarations(request: CreateMethodRequest, receiver:String, receiverTypeText: String): String {
private fun renderTypeParameterDeclarations(
request: CreateMethodRequest,
container: KtElement,
receiver: String,
receiverTypeText: String
): String {
if (request is CreateMethodFromKotlinUsageRequest && request.receiverExpression != null && request.isExtension) {
val t = if (receiver.isNotEmpty()) "$receiver " else receiver
analyze (request.receiverExpression) {
analyze (call as? KtElement ?: container) {
val receiverSymbol = request.receiverExpression.resolveExpression()
if (receiverSymbol is KtCallableSymbol && receiverSymbol.returnType is KtTypeParameterType) {
return ("<$receiverTypeText> $t")

View File

@@ -60,7 +60,7 @@ internal class CreateKotlinCallablePsiEditor(
val passedContainerElement = pointerToContainer.element
if (passedContainerElement == null) return
val shouldComputeContainerFromAnchor = if (passedContainerElement is PsiFile) passedContainerElement == anchor.containingFile
else passedContainerElement.getContainer() == anchor.getContainer()
else passedContainerElement.getContainer() == anchor.getContainer()
val insertContainer: PsiElement = if (shouldComputeContainerFromAnchor) {
(anchor.getExtractionContainers().firstOrNull() ?: return)
} else {

View File

@@ -6,7 +6,6 @@ import com.intellij.lang.jvm.actions.CreateMethodRequest
import com.intellij.lang.jvm.actions.ExpectedType
import com.intellij.lang.jvm.types.JvmReferenceType
import com.intellij.openapi.util.NlsSafe
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.psi.KtCallExpression
@@ -17,7 +16,7 @@ import org.jetbrains.kotlin.psi.KtSimpleNameExpression
* A request to create Kotlin callable from the usage in Kotlin.
*/
internal class CreateMethodFromKotlinUsageRequest (
private val functionCall: KtCallExpression,
functionCall: KtCallExpression,
modifiers: Collection<JvmModifier>,
val receiverExpression: KtExpression?,
val receiverType: KtType?, // (in case receiverExpression is null) it can be notnull when there's implicit receiver: `blah { unknownFunc() }`
@@ -25,19 +24,16 @@ internal class CreateMethodFromKotlinUsageRequest (
val isAbstractClassOrInterface: Boolean,
val isForCompanion: Boolean
) : CreateExecutableFromKotlinUsageRequest<KtCallExpression>(functionCall, modifiers), CreateMethodRequest {
private val returnType = mutableListOf<ExpectedType>()
private val returnType:List<ExpectedType> = initializeReturnType(functionCall)
init {
analyze(functionCall) {
initializeReturnType()
}
}
context (KtAnalysisSession)
private fun initializeReturnType() {
val returnJvmType = functionCall.getExpectedKotlinType() ?: return
(returnJvmType.theType as? JvmReferenceType)?.let { if (it.resolve() == null) return }
returnType.add(returnJvmType)
private fun initializeReturnType(functionCall: KtCallExpression): List<ExpectedType> {
return analyze(functionCall) {
val returnJvmType = functionCall.getExpectedKotlinType() ?: return emptyList()
if (returnJvmType !is ExpectedKotlinType) {
(returnJvmType.theType as? JvmReferenceType)?.let { if (it.resolve() == null) return emptyList() }
}
listOf(returnJvmType)
}
}
override fun isValid(): Boolean = super.isValid() && getReferenceName() != null

View File

@@ -0,0 +1,8 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.k2.codeinsight.quickFixes.createFromUsage
import com.intellij.lang.jvm.actions.ExpectedTypeWithNullability
import com.intellij.lang.jvm.types.JvmType
import org.jetbrains.kotlin.analysis.api.types.KtType
class ExpectedKotlinType(val ktType: KtType, jvmType: JvmType) : ExpectedTypeWithNullability(jvmType, ktType.nullability.toNullability())

View File

@@ -4,6 +4,7 @@ package org.jetbrains.kotlin.idea.k2.codeinsight.quickFixes.createFromUsage
import com.intellij.codeInsight.Nullability
import com.intellij.lang.jvm.JvmClass
import com.intellij.lang.jvm.actions.ExpectedParameter
import com.intellij.lang.jvm.actions.ExpectedType
import com.intellij.lang.jvm.actions.ExpectedTypeWithNullability
import com.intellij.lang.jvm.actions.expectedParameter
import com.intellij.lang.jvm.types.JvmType
@@ -27,6 +28,7 @@ import org.jetbrains.kotlin.analysis.api.renderer.types.renderers.KtFlexibleType
import org.jetbrains.kotlin.analysis.api.renderer.types.renderers.KtTypeProjectionRenderer
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.types.*
import org.jetbrains.kotlin.analysis.api.types.KtIntersectionType
import org.jetbrains.kotlin.analysis.utils.printer.PrettyPrinter
import org.jetbrains.kotlin.asJava.findFacadeClass
import org.jetbrains.kotlin.asJava.toLightClass
@@ -72,7 +74,7 @@ context (KtAnalysisSession)
internal fun KtType.convertToClass(): KtClass? = expandedClassSymbol?.psi as? KtClass
context (KtAnalysisSession)
internal fun KtElement.getExpectedKotlinType(): ExpectedTypeWithNullability? {
internal fun KtElement.getExpectedKotlinType(): ExpectedType? {
var expectedType = getExpectedType()
if (expectedType == null) {
val parent = this.parent
@@ -103,8 +105,9 @@ internal fun KtElement.getExpectedKotlinType(): ExpectedTypeWithNullability? {
expectedType = getExpectedTypeByStringTemplateEntry(this)
}
}
val jvmType = expectedType?.convertToJvmType(this) ?: return null
return ExpectedTypeWithNullability.createExpectedKotlinType(jvmType, expectedType.nullability.toNullability())
if (expectedType == null) return null
val jvmType = expectedType.convertToJvmType(this) ?: return null
return ExpectedKotlinType(expectedType, jvmType)
}
// Given: `println("a = ${A().foo()}")`
@@ -138,7 +141,7 @@ private fun getExpectedTypeByFunctionExpressionBody(expression: KtExpression): K
}
context (KtAnalysisSession)
private fun KtType.convertToJvmType(useSitePosition: PsiElement): JvmType? = asPsiType(useSitePosition, allowErrorTypes = false)
internal fun KtType.convertToJvmType(useSitePosition: PsiElement): JvmType? = asPsiType(useSitePosition, allowErrorTypes = false)
context (KtAnalysisSession)
internal fun KtExpression.getClassOfExpressionType(): PsiElement? = when (val symbol = resolveExpression()) {
@@ -154,7 +157,7 @@ internal fun KtValueArgument.getExpectedParameterInfo(parameterIndex: Int): Expe
val expectedArgumentType = argumentExpression?.getKtType()
val parameterNames = parameterNameAsString?.let { sequenceOf(it) } ?: expectedArgumentType?.let { NAME_SUGGESTER.suggestTypeNames(it) }
val parameterType = expectedArgumentType?.convertToJvmType(argumentExpression)
val expectedType = if (parameterType == null) ExpectedTypeWithNullability.INVALID_TYPE else ExpectedTypeWithNullability.createExpectedKotlinType(parameterType, expectedArgumentType.nullability.toNullability())
val expectedType = if (parameterType == null) ExpectedTypeWithNullability.INVALID_TYPE else ExpectedKotlinType(expectedArgumentType, parameterType)
val names = parameterNames?.toList()?.toTypedArray() ?: arrayOf("p$parameterIndex")
return expectedParameter(expectedType, *names)
}
@@ -236,13 +239,15 @@ internal fun JvmType.toKtType(useSitePosition: PsiElement): KtType? = when (this
}
context (KtAnalysisSession)
fun toKtTypeWithNullability(type: ExpectedTypeWithNullability, useSitePosition: PsiElement): KtType? {
val ktTypeNullability = when (type.nullability) {
fun ExpectedType.toKtTypeWithNullability(useSitePosition: PsiElement): KtType? {
val nullability = if (this is ExpectedTypeWithNullability) this.nullability else null
val ktTypeNullability = when (nullability) {
Nullability.NOT_NULL -> KtTypeNullability.NON_NULLABLE
Nullability.NULLABLE -> KtTypeNullability.NULLABLE
Nullability.UNKNOWN -> KtTypeNullability.UNKNOWN
null -> null
}
return type.type.toKtType(useSitePosition)?.withNullability(ktTypeNullability)
return theType.toKtType(useSitePosition)?.let { if (ktTypeNullability == null) it else it.withNullability(ktTypeNullability) }
}
fun KtTypeNullability.toNullability() : Nullability {
@@ -252,3 +257,40 @@ fun KtTypeNullability.toNullability() : Nullability {
KtTypeNullability.UNKNOWN -> Nullability.UNKNOWN
}
}
// inspect `type` recursively and call `predicate` on all types inside, return true if all calls returned true
context (KtAnalysisSession)
private fun accept(type: KtType?, visited: MutableSet<KtType>, predicate: (KtType) -> Boolean) : Boolean {
if (type == null || !visited.add(type)) return true
if (!predicate.invoke(type)) return false
return when (type) {
is KtClassType -> type.qualifiers.flatMap { it.typeArguments }.map { it.type}.all { accept(it, visited, predicate)}
&& (type !is KtFunctionalType || (accept(type.returnType, visited,predicate) && accept(type.receiverType, visited, predicate)))
is KtFlexibleType -> accept(type.lowerBound, visited, predicate) && accept(type.upperBound, visited, predicate)
is KtCapturedType -> accept(type.projection.type, visited, predicate)
is KtDefinitelyNotNullType -> accept(type.original, visited, predicate)
is KtIntersectionType -> type.conjuncts.all { accept(it, visited, predicate) }
else -> true
}
}
// true if this type is accessible in the newly created method
context (KtAnalysisSession)
fun isAccessibleInCreationPlace(ktType: KtType, call: KtElement?): Boolean {
if (call == null) return true
fun KtTypeParameter.getOwningTypeParameterOwner(): KtTypeParameterListOwner? {
val parameterList = parent as? KtTypeParameterList ?: return null
return parameterList.parent as? KtTypeParameterListOwner
}
return accept(ktType, mutableSetOf()) { ktLeaf ->
if (ktLeaf is KtTypeParameterType) {
// having `<T> caller(T t) { unknownMethod(t); }` the type `T` is not accessible in created method unknownMethod(t)
val owner = (ktLeaf.symbol.psi as? KtTypeParameter)?.getOwningTypeParameterOwner()
// todo must have been "insertion point" instead of `call`
owner == null || PsiTreeUtil.isAncestor(owner, call, false)
} else {
// KtErrorType means this type is unresolved in the context of container
ktLeaf !is KtErrorType
}
}
}

View File

@@ -45,22 +45,24 @@ private fun getTargetCallExpression(element: KtElement): KtCallExpression? {
private fun KtSimpleNameExpression.referenceNameOfElement(): Boolean = getReferencedNameElementType() == KtTokens.IDENTIFIER
internal fun buildRequestsAndActions(callExpression: KtCallExpression): List<IntentionAction> {
val methodRequests = buildRequests(callExpression)
val extensions = EP_NAME.extensions
return methodRequests.flatMap { (targetClass, request) ->
extensions.flatMap { ext ->
ext.createAddMethodActions(targetClass, request)
}
}.groupActionsByType(KotlinLanguage.INSTANCE)
analyze(callExpression) {
val methodRequests = buildRequests(callExpression)
val extensions = EP_NAME.extensions
return methodRequests.flatMap { (targetClass, request) ->
extensions.flatMap { ext ->
ext.createAddMethodActions(targetClass, request)
}
}.groupActionsByType(KotlinLanguage.INSTANCE)
}
}
context(KtAnalysisSession)
internal fun buildRequests(callExpression: KtCallExpression): List<Pair<JvmClass, CreateMethodRequest>> {
val calleeExpression = callExpression.calleeExpression as? KtSimpleNameExpression ?: return emptyList()
val requests = mutableListOf<Pair<JvmClass, CreateMethodRequest>>()
val receiverExpression = calleeExpression.getReceiverExpression()
// Register default create-from-usage request.
analyze(callExpression) {
// TODO: Check whether this class or file can be edited (Use `canRefactor()`).
val defaultContainerPsi = calleeExpression.getReceiverOrContainerPsiElement()
val defaultClassForReceiverOrFile = calleeExpression.getReceiverOrContainerClass(defaultContainerPsi)
@@ -116,7 +118,6 @@ internal fun buildRequests(callExpression: KtCallExpression): List<Pair<JvmClass
isForCompanion = false
))
}
}
return requests
}

View File

@@ -8,7 +8,7 @@ class A<T>(val t: T) {
var x: A<Int> by foo(t, "")
}
private fun foo(p0: T, string: String): ReadWriteProperty<Any, A<Int>> {
private fun foo(p0: T, string: String): ReadWriteProperty<Any?, A<Int>> {
<selection>TODO("Not yet implemented")<caret></selection>
}
}