New J2K: add support of lambda expressions in nullabilityAnalysis
This commit is contained in:
+40
-8
@@ -16,8 +16,11 @@ import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespace
|
|||||||
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector
|
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
import org.jetbrains.kotlin.types.Variance
|
||||||
import org.jetbrains.kotlin.types.isNullable
|
import org.jetbrains.kotlin.types.isNullable
|
||||||
|
import org.jetbrains.kotlin.types.typeUtil.builtIns
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||||
|
|
||||||
internal class BoundTypeStorage(private val analysisAnalysisContext: AnalysisContext, private val printConstraints: Boolean) {
|
internal class BoundTypeStorage(private val analysisAnalysisContext: AnalysisContext, private val printConstraints: Boolean) {
|
||||||
@@ -34,11 +37,12 @@ internal class BoundTypeStorage(private val analysisAnalysisContext: AnalysisCon
|
|||||||
expression.selectorExpression?.toBoundType(
|
expression.selectorExpression?.toBoundType(
|
||||||
boundTypeFor(expression.receiverExpression)
|
boundTypeFor(expression.receiverExpression)
|
||||||
)
|
)
|
||||||
|
is KtLabeledExpression -> expression.baseExpression?.let { boundTypeFor(it) }
|
||||||
|
is KtLambdaExpression -> lambdaBoundType(expression)
|
||||||
else -> expression.getQualifiedExpressionForSelector()?.let { boundTypeFor(it) }
|
else -> expression.getQualifiedExpressionForSelector()?.let { boundTypeFor(it) }
|
||||||
} ?: expression.toBoundType(null)
|
} ?: expression.toBoundType(null)
|
||||||
?: LiteralBoundType(expression.isNullable())
|
?: LiteralBoundType(expression.isNullable())
|
||||||
|
|
||||||
|
|
||||||
if (printConstraints) {
|
if (printConstraints) {
|
||||||
if (expression.getNextSiblingIgnoringWhitespace() !is PsiComment) {
|
if (expression.getNextSiblingIgnoringWhitespace() !is PsiComment) {
|
||||||
val comment = with(printer) {
|
val comment = with(printer) {
|
||||||
@@ -47,9 +51,36 @@ internal class BoundTypeStorage(private val analysisAnalysisContext: AnalysisCon
|
|||||||
expression.parent.addAfter(comment, expression)
|
expression.parent.addAfter(comment, expression)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
boundType
|
boundType.withForcedNullability(expression.getForcedNullability())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun lambdaBoundType(lambda: KtLambdaExpression): BoundType? {
|
||||||
|
val builtIns = lambda.getType(lambda.analyze())?.builtIns ?: return null
|
||||||
|
|
||||||
|
val descriptor = builtIns.getFunction(lambda.valueParameters.size)
|
||||||
|
val parameterBoundTypes = lambda.valueParameters.map { parameter ->
|
||||||
|
parameter.typeReference?.typeElement?.let { typeElement ->
|
||||||
|
analysisAnalysisContext.typeElementToTypeVariable[typeElement]
|
||||||
|
}?.let {
|
||||||
|
BoundTypeTypeParameter(TypeVariableBoundType(it), Variance.IN_VARIANCE)
|
||||||
|
} ?: return null
|
||||||
|
}
|
||||||
|
val returnBoundType =
|
||||||
|
BoundTypeTypeParameter(
|
||||||
|
TypeVariableBoundType(
|
||||||
|
analysisAnalysisContext.declarationToTypeVariable[lambda.functionLiteral] ?: return null
|
||||||
|
),
|
||||||
|
Variance.OUT_VARIANCE
|
||||||
|
)
|
||||||
|
return GenericBoundType(
|
||||||
|
DescriptorClassReference(descriptor),
|
||||||
|
parameterBoundTypes + returnBoundType,
|
||||||
|
forcedNullabilityTo = null,
|
||||||
|
isNull = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun boundTypeForType(
|
fun boundTypeForType(
|
||||||
type: KotlinType,
|
type: KotlinType,
|
||||||
contextBoundType: BoundType?,
|
contextBoundType: BoundType?,
|
||||||
@@ -103,12 +134,13 @@ internal class BoundTypeStorage(private val analysisAnalysisContext: AnalysisCon
|
|||||||
?.let { TypeVariableBoundType(it) }
|
?.let { TypeVariableBoundType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KtExpression.toBoundType(contextBoundType: BoundType?): BoundType? {
|
private fun KtExpression.toBoundType(contextBoundType: BoundType?): BoundType? =
|
||||||
toBoundTypeAsTypeVariable()?.also { return it }
|
run {
|
||||||
toBoundTypeAsCallExpression(contextBoundType)?.also { return it }
|
toBoundTypeAsTypeVariable()?.also { return@run it }
|
||||||
toBoundTypeAsCastExpression()?.also { return it }
|
toBoundTypeAsCallExpression(contextBoundType)?.also { return@run it }
|
||||||
return null
|
toBoundTypeAsCastExpression()?.also { return@run it }
|
||||||
}
|
return@run null
|
||||||
|
}?.withForcedNullability(getForcedNullability())
|
||||||
|
|
||||||
private fun KotlinType.toBoundType(
|
private fun KotlinType.toBoundType(
|
||||||
contextBoundType: BoundType?,
|
contextBoundType: BoundType?,
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ internal data class FunctionTarget(
|
|||||||
override val typeVariable: TypeVariable
|
override val typeVariable: TypeVariable
|
||||||
) : DeclarationTypeVariableOwner
|
) : DeclarationTypeVariableOwner
|
||||||
|
|
||||||
|
internal data class LambdaTarget(
|
||||||
|
override val target: KtFunctionLiteral,
|
||||||
|
override val typeVariable: TypeVariable
|
||||||
|
) : DeclarationTypeVariableOwner
|
||||||
|
|
||||||
internal data class ParameterTarget(
|
internal data class ParameterTarget(
|
||||||
override val target: KtParameter,
|
override val target: KtParameter,
|
||||||
override val typeVariable: TypeVariable
|
override val typeVariable: TypeVariable
|
||||||
@@ -112,7 +117,7 @@ internal class LiteralBoundType(val isNull: Boolean) : BoundType {
|
|||||||
|
|
||||||
|
|
||||||
internal class TypeVariable(
|
internal class TypeVariable(
|
||||||
val typeElement: KtTypeElement,
|
val typeElement: KtTypeElement?,
|
||||||
val classReference: ClassReference,
|
val classReference: ClassReference,
|
||||||
val typeParameters: List<TypeVariableTypeParameter>,
|
val typeParameters: List<TypeVariableTypeParameter>,
|
||||||
var nullability: Nullability
|
var nullability: Nullability
|
||||||
|
|||||||
@@ -9,12 +9,16 @@ import com.intellij.openapi.editor.RangeMarker
|
|||||||
import com.intellij.openapi.util.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
import com.intellij.psi.PsiComment
|
import com.intellij.psi.PsiComment
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import org.jetbrains.kotlin.nj2k.NewJ2kConverterContext
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.nj2k.JKElementInfoLabel
|
import org.jetbrains.kotlin.nj2k.JKElementInfoLabel
|
||||||
|
import org.jetbrains.kotlin.nj2k.NewJ2kConverterContext
|
||||||
import org.jetbrains.kotlin.nj2k.asLabel
|
import org.jetbrains.kotlin.nj2k.asLabel
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
|
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.elementsInRange
|
import org.jetbrains.kotlin.psi.psiUtil.elementsInRange
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
|
||||||
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||||
|
|
||||||
internal class ContextCreator(
|
internal class ContextCreator(
|
||||||
@@ -25,11 +29,17 @@ internal class ContextCreator(
|
|||||||
private fun KtCallableDeclaration.typeElement(): KtTypeElement? =
|
private fun KtCallableDeclaration.typeElement(): KtTypeElement? =
|
||||||
typeReference?.typeElement
|
typeReference?.typeElement
|
||||||
|
|
||||||
private fun KtElement.asTypeVariableOwner(): TypeVariableOwner? =
|
private fun KtElement.asTypeVariableOwner(): TypeVariableOwner? {
|
||||||
when (this) {
|
return when (this) {
|
||||||
is KtParameter -> typeElement()?.asTypeVariable()?.let { ParameterTarget(this, it) }
|
is KtParameter -> typeElement()?.asTypeVariable()?.let { ParameterTarget(this, it) }
|
||||||
is KtProperty -> typeElement()?.asTypeVariable()?.let { PropertyTarget(this, it) }
|
is KtProperty -> typeElement()?.asTypeVariable()?.let { PropertyTarget(this, it) }
|
||||||
is KtNamedFunction -> typeElement()?.asTypeVariable()?.let { FunctionTarget(this, it) }
|
is KtNamedFunction -> typeElement()?.asTypeVariable()?.let { FunctionTarget(this, it) }
|
||||||
|
is KtLambdaExpression -> {
|
||||||
|
val context = analyze()
|
||||||
|
val returnType = getType(context)?.arguments?.lastOrNull()?.type ?: return null
|
||||||
|
val typeVariable = returnType.asTypeVariable() ?: return null
|
||||||
|
LambdaTarget(functionLiteral, typeVariable)
|
||||||
|
}
|
||||||
|
|
||||||
is KtBinaryExpressionWithTypeRHS -> right?.typeElement
|
is KtBinaryExpressionWithTypeRHS -> right?.typeElement
|
||||||
?.takeIf { KtPsiUtil.isUnsafeCast(this) }
|
?.takeIf { KtPsiUtil.isUnsafeCast(this) }
|
||||||
@@ -45,6 +55,7 @@ internal class ContextCreator(
|
|||||||
else null
|
else null
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun createContext(analysisScope: AnalysisScope): AnalysisContext {
|
fun createContext(analysisScope: AnalysisScope): AnalysisContext {
|
||||||
val typeVariableOwners = analysisScope.flatMap {
|
val typeVariableOwners = analysisScope.flatMap {
|
||||||
@@ -56,7 +67,8 @@ internal class ContextCreator(
|
|||||||
val typeElementsToTypeVariables =
|
val typeElementsToTypeVariables =
|
||||||
typeVariableOwners.flatMap {
|
typeVariableOwners.flatMap {
|
||||||
it.innerTypeVariables() + it.allTypeVariables
|
it.innerTypeVariables() + it.allTypeVariables
|
||||||
}.associateBy { it.typeElement }
|
}.filter { it.typeElement != null }
|
||||||
|
.associateBy { it.typeElement!! }
|
||||||
|
|
||||||
val declarationToTypeVariable = typeVariableOwners.asSequence()
|
val declarationToTypeVariable = typeVariableOwners.asSequence()
|
||||||
.mapNotNull { owner ->
|
.mapNotNull { owner ->
|
||||||
@@ -82,6 +94,22 @@ internal class ContextCreator(
|
|||||||
}
|
}
|
||||||
return TypeVariable(this, classReference, typeParameters, nullability)
|
return TypeVariable(this, classReference, typeParameters, nullability)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun KotlinType.asTypeVariable(): TypeVariable? {
|
||||||
|
val classReference = constructor
|
||||||
|
.declarationDescriptor
|
||||||
|
?.safeAs<ClassDescriptor>()
|
||||||
|
?.let { DescriptorClassReference(it) }
|
||||||
|
?: return null
|
||||||
|
val typeParameters =
|
||||||
|
arguments.zip(constructor.parameters) { argument, parameter ->
|
||||||
|
TypeVariableTypeParameterWithTypeParameter(
|
||||||
|
argument.type.asTypeVariable() ?: return null,
|
||||||
|
parameter.variance
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return TypeVariable(null, classReference, typeParameters, Nullability.UNKNOWN)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -22,7 +22,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
|||||||
|
|
||||||
|
|
||||||
internal fun TypeVariable.changeNullability(toNullable: Boolean) {
|
internal fun TypeVariable.changeNullability(toNullable: Boolean) {
|
||||||
typeElement.changeNullability(toNullable)
|
typeElement?.changeNullability(toNullable)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun KtTypeElement.changeNullability(toNullable: Boolean) {
|
internal fun KtTypeElement.changeNullability(toNullable: Boolean) {
|
||||||
@@ -40,6 +40,7 @@ internal fun AnalysisContext.fixTypeVariablesNullability() {
|
|||||||
if (typeElementToTypeVariable.isEmpty()) return
|
if (typeElementToTypeVariable.isEmpty()) return
|
||||||
|
|
||||||
val deepComparator = Comparator<TypeVariable> { o1, o2 ->
|
val deepComparator = Comparator<TypeVariable> { o1, o2 ->
|
||||||
|
if (o1.typeElement == null || o2.typeElement == null) return@Comparator -1
|
||||||
if (o1.typeElement.isAncestor(o2.typeElement)) 1 else -1
|
if (o1.typeElement.isAncestor(o2.typeElement)) 1 else -1
|
||||||
}
|
}
|
||||||
for (typeVariableOwner in typeVariableOwners) {
|
for (typeVariableOwner in typeVariableOwners) {
|
||||||
|
|||||||
@@ -70,9 +70,13 @@ internal class Printer(private val analysisContext: AnalysisContext) {
|
|||||||
|
|
||||||
|
|
||||||
private class Namer(analysisContext: AnalysisContext) {
|
private class Namer(analysisContext: AnalysisContext) {
|
||||||
val names = analysisContext.typeElementToTypeVariable.values.mapIndexed { index, typeVariable ->
|
val names = run {
|
||||||
typeVariable to "T$index"
|
val typeVariables = (analysisContext.typeElementToTypeVariable.values +
|
||||||
}.toMap()
|
analysisContext.declarationToTypeVariable.values)
|
||||||
|
typeVariables.mapIndexed { index, typeVariable ->
|
||||||
|
typeVariable to "T$index"
|
||||||
|
}.toMap()
|
||||||
|
}
|
||||||
|
|
||||||
fun name(typeVariable: TypeVariable): String =
|
fun name(typeVariable: TypeVariable): String =
|
||||||
names.getValue(typeVariable)
|
names.getValue(typeVariable)
|
||||||
|
|||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
fun notNullParameters(f: Function2<Int, Int, String>) {}
|
||||||
|
fun nullableParameter(f: Function2<Int, Int, String>) {}
|
||||||
|
fun nullableReturnType(f: Function2<Int, Int, String>) {}
|
||||||
|
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
notNullParameters({ i: Int, j: Int ->
|
||||||
|
if (i < 10 && j > 0) "" else ""
|
||||||
|
})
|
||||||
|
|
||||||
|
nullableParameter({ i: Int, j: Int ->
|
||||||
|
if (i == null) "" else ""
|
||||||
|
})
|
||||||
|
|
||||||
|
nullableReturnType({ i: Int, j: Int ->
|
||||||
|
if (i < 10) return@nullableReturnType null
|
||||||
|
return@nullableReturnType "nya"
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
fun notNullParameters(f: Function2<Int, Int, String>) {}
|
||||||
|
fun nullableParameter(f: Function2<Int?, Int, String>) {}
|
||||||
|
fun nullableReturnType(f: Function2<Int, Int, String?>) {}
|
||||||
|
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
notNullParameters({ i: Int, j: Int ->
|
||||||
|
if (i < 10 && j > 0) "" else ""
|
||||||
|
})
|
||||||
|
|
||||||
|
nullableParameter({ i: Int?, j: Int ->
|
||||||
|
if (i == null) "" else ""
|
||||||
|
})
|
||||||
|
|
||||||
|
nullableReturnType({ i: Int, j: Int ->
|
||||||
|
if (i < 10) return@nullableReturnType null
|
||||||
|
return@nullableReturnType "nya"
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -44,6 +44,11 @@ public class NullabilityAnalysisTestGenerated extends AbstractNullabilityAnalysi
|
|||||||
runTest("nj2k/testData/nullabilityAnalysis/functionTypeParameterNullability.kt");
|
runTest("nj2k/testData/nullabilityAnalysis/functionTypeParameterNullability.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("functions.kt")
|
||||||
|
public void testFunctions() throws Exception {
|
||||||
|
runTest("nj2k/testData/nullabilityAnalysis/functions.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("loops.kt")
|
@TestMetadata("loops.kt")
|
||||||
public void testLoops() throws Exception {
|
public void testLoops() throws Exception {
|
||||||
runTest("nj2k/testData/nullabilityAnalysis/loops.kt");
|
runTest("nj2k/testData/nullabilityAnalysis/loops.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user