New J2K: add support of lambda expressions in nullabilityAnalysis

This commit is contained in:
Ilya Kirillov
2019-04-22 14:31:30 +03:00
parent 1ffe07bb6c
commit 7e7e8f7631
8 changed files with 130 additions and 17 deletions
@@ -16,8 +16,11 @@ import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespace
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny
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.Variance
import org.jetbrains.kotlin.types.isNullable
import org.jetbrains.kotlin.types.typeUtil.builtIns
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
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(
boundTypeFor(expression.receiverExpression)
)
is KtLabeledExpression -> expression.baseExpression?.let { boundTypeFor(it) }
is KtLambdaExpression -> lambdaBoundType(expression)
else -> expression.getQualifiedExpressionForSelector()?.let { boundTypeFor(it) }
} ?: expression.toBoundType(null)
?: LiteralBoundType(expression.isNullable())
if (printConstraints) {
if (expression.getNextSiblingIgnoringWhitespace() !is PsiComment) {
val comment = with(printer) {
@@ -47,9 +51,36 @@ internal class BoundTypeStorage(private val analysisAnalysisContext: AnalysisCon
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(
type: KotlinType,
contextBoundType: BoundType?,
@@ -103,12 +134,13 @@ internal class BoundTypeStorage(private val analysisAnalysisContext: AnalysisCon
?.let { TypeVariableBoundType(it) }
}
private fun KtExpression.toBoundType(contextBoundType: BoundType?): BoundType? {
toBoundTypeAsTypeVariable()?.also { return it }
toBoundTypeAsCallExpression(contextBoundType)?.also { return it }
toBoundTypeAsCastExpression()?.also { return it }
return null
}
private fun KtExpression.toBoundType(contextBoundType: BoundType?): BoundType? =
run {
toBoundTypeAsTypeVariable()?.also { return@run it }
toBoundTypeAsCallExpression(contextBoundType)?.also { return@run it }
toBoundTypeAsCastExpression()?.also { return@run it }
return@run null
}?.withForcedNullability(getForcedNullability())
private fun KotlinType.toBoundType(
contextBoundType: BoundType?,
@@ -47,6 +47,11 @@ internal data class FunctionTarget(
override val typeVariable: TypeVariable
) : DeclarationTypeVariableOwner
internal data class LambdaTarget(
override val target: KtFunctionLiteral,
override val typeVariable: TypeVariable
) : DeclarationTypeVariableOwner
internal data class ParameterTarget(
override val target: KtParameter,
override val typeVariable: TypeVariable
@@ -112,7 +117,7 @@ internal class LiteralBoundType(val isNull: Boolean) : BoundType {
internal class TypeVariable(
val typeElement: KtTypeElement,
val typeElement: KtTypeElement?,
val classReference: ClassReference,
val typeParameters: List<TypeVariableTypeParameter>,
var nullability: Nullability
@@ -9,12 +9,16 @@ import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiComment
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.NewJ2kConverterContext
import org.jetbrains.kotlin.nj2k.asLabel
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
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
internal class ContextCreator(
@@ -25,11 +29,17 @@ internal class ContextCreator(
private fun KtCallableDeclaration.typeElement(): KtTypeElement? =
typeReference?.typeElement
private fun KtElement.asTypeVariableOwner(): TypeVariableOwner? =
when (this) {
private fun KtElement.asTypeVariableOwner(): TypeVariableOwner? {
return when (this) {
is KtParameter -> typeElement()?.asTypeVariable()?.let { ParameterTarget(this, it) }
is KtProperty -> typeElement()?.asTypeVariable()?.let { PropertyTarget(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
?.takeIf { KtPsiUtil.isUnsafeCast(this) }
@@ -45,6 +55,7 @@ internal class ContextCreator(
else null
else -> null
}
}
fun createContext(analysisScope: AnalysisScope): AnalysisContext {
val typeVariableOwners = analysisScope.flatMap {
@@ -56,7 +67,8 @@ internal class ContextCreator(
val typeElementsToTypeVariables =
typeVariableOwners.flatMap {
it.innerTypeVariables() + it.allTypeVariables
}.associateBy { it.typeElement }
}.filter { it.typeElement != null }
.associateBy { it.typeElement!! }
val declarationToTypeVariable = typeVariableOwners.asSequence()
.mapNotNull { owner ->
@@ -82,6 +94,22 @@ internal class ContextCreator(
}
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)
}
}
@@ -22,7 +22,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs
internal fun TypeVariable.changeNullability(toNullable: Boolean) {
typeElement.changeNullability(toNullable)
typeElement?.changeNullability(toNullable)
}
internal fun KtTypeElement.changeNullability(toNullable: Boolean) {
@@ -40,6 +40,7 @@ internal fun AnalysisContext.fixTypeVariablesNullability() {
if (typeElementToTypeVariable.isEmpty()) return
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
}
for (typeVariableOwner in typeVariableOwners) {
@@ -70,9 +70,13 @@ internal class Printer(private val analysisContext: AnalysisContext) {
private class Namer(analysisContext: AnalysisContext) {
val names = analysisContext.typeElementToTypeVariable.values.mapIndexed { index, typeVariable ->
typeVariable to "T$index"
}.toMap()
val names = run {
val typeVariables = (analysisContext.typeElementToTypeVariable.values +
analysisContext.declarationToTypeVariable.values)
typeVariables.mapIndexed { index, typeVariable ->
typeVariable to "T$index"
}.toMap()
}
fun name(typeVariable: TypeVariable): String =
names.getValue(typeVariable)
+19
View File
@@ -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"
})
}
+19
View File
@@ -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");
}
@TestMetadata("functions.kt")
public void testFunctions() throws Exception {
runTest("nj2k/testData/nullabilityAnalysis/functions.kt");
}
@TestMetadata("loops.kt")
public void testLoops() throws Exception {
runTest("nj2k/testData/nullabilityAnalysis/loops.kt");