New J2K: Add nullability analysis to post processing
This commit is contained in:
committed by
Ilya Kirillov
parent
63d3f90373
commit
b6625c536d
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.nj2k
|
||||
import com.intellij.codeInsight.actions.OptimizeImportsProcessor
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.editor.RangeMarker
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
@@ -37,8 +38,11 @@ import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.j2k.ConverterSettings
|
||||
import org.jetbrains.kotlin.j2k.PostProcessor
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.nj2k.*
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.nj2k.nullabilityAnalysis.AnalysisScope
|
||||
import org.jetbrains.kotlin.nj2k.nullabilityAnalysis.NullabilityAnalysisFacade
|
||||
import org.jetbrains.kotlin.nj2k.nullabilityAnalysis.nullabilityByUndefinedNullabilityComment
|
||||
import org.jetbrains.kotlin.nj2k.nullabilityAnalysis.prepareTypeElementByMakingAllTypesNullableConsideringNullabilityComment
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.psiUtil.elementsInRange
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
|
||||
@@ -100,13 +104,18 @@ class NewJ2kPostProcessor(
|
||||
|
||||
|
||||
override fun doAdditionalProcessing(file: KtFile, rangeMarker: RangeMarker?) {
|
||||
OptimizeImportsProcessor(file.project, file.containingKtFile).run()
|
||||
|
||||
runBlocking(EDT.ModalityStateElement(ModalityState.defaultModalityState())) {
|
||||
NewJ2KPostProcessingRegistrar.mainProcessings.runProcessings(file, rangeMarker)
|
||||
|
||||
|
||||
if (formatCode) {
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
runBlocking(EDT.ModalityStateElement(ModalityState.defaultModalityState())) {
|
||||
withContext(EDT) {
|
||||
NullabilityAnalysisFacade(
|
||||
getTypeElementNullability = ::nullabilityByUndefinedNullabilityComment,
|
||||
prepareTypeElement = ::prepareTypeElementByMakingAllTypesNullableConsideringNullabilityComment,
|
||||
debugPrint = false
|
||||
).fixNullability(AnalysisScope(file))
|
||||
}
|
||||
withContext(EDT) {
|
||||
NewJ2KPostProcessingRegistrar.mainProcessings.runProcessings(file, rangeMarker)
|
||||
}
|
||||
withContext(EDT) {
|
||||
runWriteAction {
|
||||
val codeStyleManager = CodeStyleManager.getInstance(file.project)
|
||||
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.nj2k.nullabilityAnalysis
|
||||
|
||||
import com.intellij.psi.PsiComment
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
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.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.isNullable
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
internal class BoundTypeStorage(private val analysisAnalysisContext: AnalysisContext, private val printConstraints: Boolean) {
|
||||
private val cache = mutableMapOf<KtExpression, BoundType>()
|
||||
private val printer = Printer(analysisAnalysisContext)
|
||||
|
||||
fun boundTypeFor(expression: KtExpression): BoundType =
|
||||
cache.getOrPut(expression) {
|
||||
if (expression is KtParenthesizedExpression) return@getOrPut boundTypeFor(expression.expression!!)
|
||||
val boundType =
|
||||
when (expression) {
|
||||
is KtParenthesizedExpression -> expression.expression?.let { boundTypeFor(it) }
|
||||
is KtQualifiedExpression ->
|
||||
expression.selectorExpression?.toBoundType(
|
||||
boundTypeFor(expression.receiverExpression)
|
||||
)
|
||||
else -> expression.getQualifiedExpressionForSelector()?.let { boundTypeFor(it) }
|
||||
} ?: expression.toBoundType(null)
|
||||
?: LiteralBoundType(expression.isNullable())
|
||||
|
||||
|
||||
if (printConstraints) {
|
||||
if (expression.getNextSiblingIgnoringWhitespace() !is PsiComment) {
|
||||
val comment = with(printer) {
|
||||
KtPsiFactory(expression.project).createComment("/*${boundType.asString()}*/")
|
||||
}
|
||||
expression.parent.addAfter(comment, expression)
|
||||
}
|
||||
}
|
||||
boundType
|
||||
}
|
||||
|
||||
fun boundTypeForType(
|
||||
type: KotlinType,
|
||||
contextBoundType: BoundType?,
|
||||
typeParameterDescriptors: Map<TypeParameterDescriptor, TypeVariable>
|
||||
): BoundType? =
|
||||
type.toBoundType(contextBoundType, typeParameterDescriptors)
|
||||
|
||||
private fun KtExpression.resolveToTypeVariable(): TypeVariable? =
|
||||
getCalleeExpressionIfAny()
|
||||
?.safeAs<KtReferenceExpression>()
|
||||
?.resolve()
|
||||
?.safeAs<KtDeclaration>()
|
||||
?.let {
|
||||
analysisAnalysisContext.declarationToTypeVariable[it]
|
||||
} ?: KtPsiUtil.deparenthesize(this)?.let { analysisAnalysisContext.declarationToTypeVariable[it] }
|
||||
|
||||
private fun KtExpression.toBoundTypeAsTypeVariable(): BoundType? =
|
||||
resolveToTypeVariable()?.let { TypeVariableBoundType(it, getForcedNullability()) }
|
||||
|
||||
private fun KtExpression.toBoundTypeAsCallExpression(contextBoundType: BoundType?): BoundType? {
|
||||
val typeElement = getCalleeExpressionIfAny()
|
||||
?.safeAs<KtReferenceExpression>()
|
||||
?.resolve()
|
||||
?.safeAs<KtCallableDeclaration>()
|
||||
?.typeReference
|
||||
?.typeElement
|
||||
typeElement?.let { analysisAnalysisContext.typeElementToTypeVariable[it] }?.also {
|
||||
return TypeVariableBoundType(it)
|
||||
}
|
||||
val bindingContext = analyze()
|
||||
val descriptor =
|
||||
getResolvedCall(bindingContext)?.candidateDescriptor?.original?.safeAs<CallableDescriptor>() ?: return null
|
||||
val typeParameters =
|
||||
if (this is KtCallElement) {
|
||||
typeArguments.mapIndexed { index, typeArgument ->
|
||||
//TODO better check
|
||||
descriptor.typeParameters[index] to
|
||||
analysisAnalysisContext.typeElementToTypeVariable.getValue(typeArgument.typeReference?.typeElement!!)
|
||||
}.toMap()
|
||||
} else emptyMap()
|
||||
return descriptor.returnType?.toBoundType(contextBoundType, typeParameters)
|
||||
}
|
||||
|
||||
private fun KtExpression.toBoundTypeAsCastExpression(): BoundType? {
|
||||
val castExpression = KtPsiUtil.deparenthesize(this)
|
||||
?.safeAs<KtBinaryExpressionWithTypeRHS>()
|
||||
?.takeIf { KtPsiUtil.isUnsafeCast(it) }
|
||||
?: return null
|
||||
return castExpression.right?.typeElement
|
||||
?.let { analysisAnalysisContext.typeElementToTypeVariable[it] }
|
||||
?.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 KotlinType.toBoundType(
|
||||
contextBoundType: BoundType?,
|
||||
typeParameterDescriptors: Map<TypeParameterDescriptor, TypeVariable>
|
||||
): BoundType? {
|
||||
fun KotlinType.toBoundType(): BoundType? {
|
||||
val forcedNullability = when {
|
||||
isMarkedNullable -> Nullability.NULLABLE
|
||||
else -> null
|
||||
}
|
||||
val target = constructor.declarationDescriptor
|
||||
return when (target) {
|
||||
is ClassDescriptor -> {
|
||||
val classReference = DescriptorClassReference(target)
|
||||
GenericBoundType(
|
||||
classReference,
|
||||
(arguments zip constructor.parameters).map { (typeArgument, typeParameter) ->
|
||||
BoundTypeTypeParameter(
|
||||
typeArgument.type.toBoundType() ?: return null,
|
||||
typeParameter.variance
|
||||
)
|
||||
},
|
||||
forcedNullability,
|
||||
isNullable()
|
||||
)
|
||||
|
||||
}
|
||||
is TypeParameterDescriptor -> {
|
||||
when {
|
||||
target in typeParameterDescriptors ->
|
||||
TypeVariableBoundType(typeParameterDescriptors.getValue(target), forcedNullability)
|
||||
contextBoundType != null ->
|
||||
contextBoundType.typeParameters.getOrNull(target.index)?.boundType?.withForcedNullability(forcedNullability)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
else -> error(toString())
|
||||
}
|
||||
}
|
||||
return toBoundType()
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.nj2k.nullabilityAnalysis
|
||||
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.types.Variance
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
internal class ConstraintBuilder(val boundTypeStorage: BoundTypeStorage) {
|
||||
private val constraints = mutableListOf<Constraint>()
|
||||
|
||||
internal fun getConstraints(): List<Constraint> = constraints
|
||||
|
||||
inline fun KtExpression.addSubtypeNullabilityConstraint(typeVariable: TypeVariable, cameFrom: ConstraintCameFrom) {
|
||||
addSubtypeNullabilityConstraint(TypeVariableBoundType(typeVariable), cameFrom)
|
||||
}
|
||||
|
||||
inline fun KtExpression.addEqualsNullabilityConstraint(nullability: Nullability, cameFrom: ConstraintCameFrom) {
|
||||
val boundType = boundTypeStorage.boundTypeFor(this).safeAs<TypeVariableBoundType>()
|
||||
?.withForcedNullability(getForcedNullability())
|
||||
?: return
|
||||
constraints += EqualConstraint(boundType.bound, LiteralBound(nullability), cameFrom)
|
||||
}
|
||||
|
||||
inline fun TypeVariable.addEqualsNullabilityConstraint(other: BoundType, cameFrom: ConstraintCameFrom) {
|
||||
TypeVariableBoundType(this).isTheSameType(other, cameFrom)
|
||||
}
|
||||
|
||||
inline fun KtExpression.addSubtypeNullabilityConstraint(upperTypeExpression: KtExpression, cameFrom: ConstraintCameFrom) {
|
||||
boundTypeStorage.boundTypeFor(this).subtypeOf(boundTypeStorage.boundTypeFor(upperTypeExpression), cameFrom)
|
||||
}
|
||||
|
||||
inline fun KtExpression.addSubtypeNullabilityConstraint(upperBoundType: BoundType, cameFrom: ConstraintCameFrom) {
|
||||
boundTypeStorage.boundTypeFor(this)
|
||||
.withForcedNullability(getForcedNullability())
|
||||
.subtypeOf(upperBoundType, cameFrom)
|
||||
}
|
||||
|
||||
inline fun TypeVariable.subtypeOf(other: BoundType, cameFrom: ConstraintCameFrom) {
|
||||
TypeVariableBoundType(this).subtypeOf(other, cameFrom)
|
||||
}
|
||||
|
||||
fun BoundType.subtypeOf(other: BoundType, cameFrom: ConstraintCameFrom) {
|
||||
(typeParameters zip other.typeParameters).forEach { (left, right) ->
|
||||
val variance = left.variance
|
||||
when (variance) {
|
||||
Variance.OUT_VARIANCE -> left.boundType.subtypeOf(right.boundType, cameFrom)
|
||||
Variance.IN_VARIANCE -> right.boundType.subtypeOf(left.boundType, cameFrom)
|
||||
Variance.INVARIANT -> right.boundType.isTheSameType(left.boundType, cameFrom)
|
||||
}
|
||||
}
|
||||
|
||||
constraints += SubtypeConstraint(bound, other.bound, cameFrom)
|
||||
}
|
||||
|
||||
fun BoundType.isTheSameType(other: BoundType, cameFrom: ConstraintCameFrom) {
|
||||
(typeParameters zip other.typeParameters).forEach { (left, right) ->
|
||||
left.boundType.isTheSameType(right.boundType, cameFrom)
|
||||
}
|
||||
constraints += EqualConstraint(bound, other.bound, cameFrom)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.nj2k.nullabilityAnalysis
|
||||
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiTypeParameter
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.types.Variance
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
|
||||
internal interface TypeVariableOwner {
|
||||
val target: KtElement
|
||||
}
|
||||
|
||||
internal interface SingleTypeVariableOwner : TypeVariableOwner {
|
||||
val typeVariable: TypeVariable
|
||||
}
|
||||
|
||||
internal interface MultipleTypeVariablesOwner : TypeVariableOwner {
|
||||
val typeVariables: List<TypeVariable>
|
||||
}
|
||||
|
||||
internal val TypeVariableOwner.allTypeVariables
|
||||
get() = when (this) {
|
||||
is SingleTypeVariableOwner -> listOf(typeVariable)
|
||||
is MultipleTypeVariablesOwner -> typeVariables
|
||||
else -> error("TypeVariableOwner should be either SingleTypeVariableOwner or MultipleTypeVariablesOwner")
|
||||
}
|
||||
|
||||
|
||||
internal interface DeclarationTypeVariableOwner : SingleTypeVariableOwner {
|
||||
override val target: KtCallableDeclaration
|
||||
}
|
||||
|
||||
internal data class PropertyTarget(
|
||||
override val target: KtProperty,
|
||||
override val typeVariable: TypeVariable
|
||||
) : DeclarationTypeVariableOwner
|
||||
|
||||
internal data class FunctionTarget(
|
||||
override val target: KtNamedFunction,
|
||||
override val typeVariable: TypeVariable
|
||||
) : DeclarationTypeVariableOwner
|
||||
|
||||
internal data class ParameterTarget(
|
||||
override val target: KtParameter,
|
||||
override val typeVariable: TypeVariable
|
||||
) : DeclarationTypeVariableOwner
|
||||
|
||||
internal data class TypeCastTarget(
|
||||
override val target: KtBinaryExpressionWithTypeRHS,
|
||||
override val typeVariable: TypeVariable
|
||||
) : SingleTypeVariableOwner
|
||||
|
||||
internal data class FunctionCallTypeArgumentTarget(
|
||||
override val target: KtCallExpression,
|
||||
override val typeVariables: List<TypeVariable>
|
||||
) : MultipleTypeVariablesOwner
|
||||
|
||||
|
||||
internal interface ClassReference
|
||||
internal data class KtClassReference(val klass: KtClassOrObject) : ClassReference
|
||||
internal data class DescriptorClassReference(val descriptor: ClassDescriptor) : ClassReference
|
||||
internal data class JavaClassReference(val klass: PsiClass) : ClassReference
|
||||
internal data class TypeParameterClassReference(val typeParameter: KtTypeParameter) : ClassReference
|
||||
internal data class UnknownClassReference(val text: String) : ClassReference
|
||||
internal object LiteralClassReference : ClassReference
|
||||
|
||||
|
||||
internal interface BoundType {
|
||||
val classReference: ClassReference
|
||||
val typeParameters: List<BoundTypeTypeParameter>
|
||||
val forcedNullabilityTo: Nullability?
|
||||
}
|
||||
|
||||
internal class TypeVariableBoundType(
|
||||
val typeVariable: TypeVariable,
|
||||
override val forcedNullabilityTo: Nullability? = null
|
||||
) : BoundType {
|
||||
override val classReference: ClassReference = typeVariable.classReference
|
||||
override val typeParameters: List<BoundTypeTypeParameter> =
|
||||
typeVariable.typeParameters.map { typeParameter ->
|
||||
val boundType =
|
||||
if (typeParameter is TypeVariableTypeParameterWithTypeParameter)
|
||||
TypeVariableBoundType(typeParameter.typeVariable)
|
||||
else StarProjectionBoundType
|
||||
|
||||
BoundTypeTypeParameter(
|
||||
boundType,
|
||||
typeParameter.variance //TODO isMarkedNullable
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal class GenericBoundType(
|
||||
override val classReference: ClassReference,
|
||||
override val typeParameters: List<BoundTypeTypeParameter>,
|
||||
override val forcedNullabilityTo: Nullability? = null,
|
||||
val isNull: Boolean
|
||||
) : BoundType
|
||||
|
||||
internal class LiteralBoundType(val isNull: Boolean) : BoundType {
|
||||
override val classReference = LiteralClassReference
|
||||
override val typeParameters = emptyList()
|
||||
override val forcedNullabilityTo: Nullability? = null
|
||||
}
|
||||
|
||||
|
||||
internal class TypeVariable(
|
||||
val typeElement: KtTypeElement,
|
||||
val classReference: ClassReference,
|
||||
val typeParameters: List<TypeVariableTypeParameter>,
|
||||
var nullability: Nullability
|
||||
)
|
||||
|
||||
internal inline val BoundType.bound
|
||||
get() =
|
||||
when {
|
||||
forcedNullabilityTo != null -> LiteralBound(forcedNullabilityTo!!)
|
||||
this is TypeVariableBoundType -> TypeVariableBound(typeVariable)
|
||||
this is LiteralBoundType -> LiteralBound(isNull.nullableForTrue())
|
||||
this is GenericBoundType -> LiteralBound(isNull.nullableForTrue())
|
||||
this is StarProjectionBoundType -> LiteralBound(Nullability.NULLABLE)
|
||||
else -> error("Bad bound type $this")
|
||||
}
|
||||
|
||||
internal inline fun <reified T : BoundType> T.withForcedNullability(nullability: Nullability?): T =
|
||||
if (forcedNullabilityTo == nullability) this
|
||||
else when (this) {
|
||||
is GenericBoundType ->
|
||||
GenericBoundType(
|
||||
classReference,
|
||||
typeParameters,
|
||||
nullability,
|
||||
isNull
|
||||
)
|
||||
is TypeVariableBoundType ->
|
||||
TypeVariableBoundType(
|
||||
typeVariable,
|
||||
nullability
|
||||
)
|
||||
is LiteralBoundType -> this
|
||||
else -> this
|
||||
} as T
|
||||
|
||||
internal interface TypeVariableTypeParameter {
|
||||
val variance: Variance
|
||||
}
|
||||
|
||||
internal data class TypeVariableTypeParameterWithTypeParameter(
|
||||
val typeVariable: TypeVariable,
|
||||
override val variance: Variance
|
||||
) : TypeVariableTypeParameter
|
||||
|
||||
internal object TypeVariableStartProjectionTypeParameter : TypeVariableTypeParameter {
|
||||
override val variance = Variance.INVARIANT //TODO ??
|
||||
}
|
||||
|
||||
|
||||
internal data class BoundTypeTypeParameter(
|
||||
val boundType: BoundType,
|
||||
val variance: Variance
|
||||
)
|
||||
|
||||
internal object StarProjectionBoundType : BoundType {
|
||||
override val classReference = UnknownClassReference("*")//TODO
|
||||
override val typeParameters = emptyList()
|
||||
override val forcedNullabilityTo = null
|
||||
}
|
||||
|
||||
internal interface Constraint {
|
||||
val cameFrom: ConstraintCameFrom
|
||||
}
|
||||
|
||||
interface ConstraintBound
|
||||
|
||||
internal data class TypeVariableBound(val typeVariable: TypeVariable) : ConstraintBound
|
||||
internal data class LiteralBound(val nullability: Nullability) : ConstraintBound
|
||||
|
||||
internal data class SubtypeConstraint(
|
||||
var lowerBound: ConstraintBound,
|
||||
var upperBound: ConstraintBound,
|
||||
override val cameFrom: ConstraintCameFrom
|
||||
) : Constraint
|
||||
|
||||
enum class ConstraintCameFrom {
|
||||
SUPER_DECLARATION,
|
||||
INITIALIZER,
|
||||
COMPARED_WITH_NULL,
|
||||
ASSIGNMENT_TARGET,
|
||||
USED_AS_RECEIVER,
|
||||
PARAMETER_PASSED
|
||||
}
|
||||
|
||||
internal data class EqualConstraint(
|
||||
val leftBound: ConstraintBound,
|
||||
val rightBound: ConstraintBound,
|
||||
override val cameFrom: ConstraintCameFrom
|
||||
) : Constraint
|
||||
|
||||
|
||||
private fun PsiTypeParameter.variance() = Variance.INVARIANT//TODO real variance
|
||||
|
||||
internal fun ClassReference.typeParametersVariance(parameterIndex: Int): Variance =
|
||||
when (this) {
|
||||
is KtClassReference -> klass.typeParameters.getOrNull(parameterIndex)?.variance ?: Variance.INVARIANT
|
||||
is JavaClassReference -> klass.typeParameters.getOrNull(parameterIndex)?.variance() ?: Variance.INVARIANT
|
||||
is DescriptorClassReference -> descriptor.typeConstructor.parameters[parameterIndex].variance
|
||||
is TypeParameterClassReference -> Variance.INVARIANT
|
||||
is UnknownClassReference -> Variance.INVARIANT
|
||||
else -> error("Bad class reference `$this`")
|
||||
}
|
||||
|
||||
internal fun TypeVariable.setNullabilityIfNotFixed(nullability: Nullability) {
|
||||
if (!isFixed) {
|
||||
this.nullability = nullability
|
||||
}
|
||||
}
|
||||
|
||||
internal val TypeVariable.isFixed: Boolean
|
||||
get() = nullability != Nullability.UNKNOWN
|
||||
|
||||
enum class Nullability {
|
||||
NULLABLE, NOT_NULL, UNKNOWN
|
||||
}
|
||||
|
||||
internal fun Boolean.nullableForTrue() =
|
||||
if (this) Nullability.NULLABLE else Nullability.NOT_NULL
|
||||
|
||||
|
||||
internal fun TypeVariableOwner.innerTypeVariables(): List<TypeVariable> =
|
||||
when (this) {
|
||||
is SingleTypeVariableOwner -> typeVariable.innerTypeVariables()
|
||||
is MultipleTypeVariablesOwner -> typeVariables.flatMap { it.innerTypeVariables() }
|
||||
else -> error("")
|
||||
}
|
||||
|
||||
internal fun TypeVariable.innerTypeVariables(): List<TypeVariable> =
|
||||
typeParameters.flatMap { typeParameter ->
|
||||
typeParameter.safeAs<TypeVariableTypeParameterWithTypeParameter>()?.let { typeVariableTypeParameter ->
|
||||
typeVariableTypeParameter.typeVariable.innerTypeVariables() + listOfNotNull(typeVariableTypeParameter.typeVariable)
|
||||
}.orEmpty()
|
||||
}
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.nj2k.nullabilityAnalysis
|
||||
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression
|
||||
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.asAssignment
|
||||
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
|
||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.getTargetFunction
|
||||
import org.jetbrains.kotlin.resolve.calls.components.isVararg
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.firstOverridden
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
internal class ConstraintsCollector(
|
||||
private val analysisContext: AnalysisContext,
|
||||
printConstraints: Boolean
|
||||
) {
|
||||
private val boundTypeStorage = BoundTypeStorage(analysisContext, printConstraints)
|
||||
private val constraintBuilder = ConstraintBuilder(boundTypeStorage)
|
||||
|
||||
private inline val KtExpression.boundType
|
||||
get() = boundTypeStorage.boundTypeFor(this)
|
||||
|
||||
|
||||
internal fun collectConstraints(analysisScope: AnalysisScope): List<Constraint> {
|
||||
analysisScope.forEach { element ->
|
||||
element.forEachDescendantOfType<KtExpression> { expression ->
|
||||
collectConstraintsForExpression(expression)
|
||||
}
|
||||
}
|
||||
return constraintBuilder.getConstraints()
|
||||
}
|
||||
|
||||
private fun collectConstraintsForExpression(expression: KtExpression) = with(constraintBuilder) {
|
||||
when {
|
||||
expression is KtQualifiedExpression -> {
|
||||
expression.receiverExpression.addEqualsNullabilityConstraint(Nullability.NOT_NULL, ConstraintCameFrom.USED_AS_RECEIVER)
|
||||
}
|
||||
|
||||
expression is KtBinaryExpressionWithTypeRHS && KtPsiUtil.isUnsafeCast(expression) -> {
|
||||
expression.right?.typeElement?.let { analysisContext.typeElementToTypeVariable[it] }?.also { typeVariable ->
|
||||
expression.left.addSubtypeNullabilityConstraint(typeVariable, ConstraintCameFrom.ASSIGNMENT_TARGET)
|
||||
}
|
||||
}
|
||||
|
||||
expression is KtBinaryExpression && expression.asAssignment() != null -> {
|
||||
expression.right?.addSubtypeNullabilityConstraint(expression.left!!, ConstraintCameFrom.ASSIGNMENT_TARGET)
|
||||
}
|
||||
|
||||
expression is KtBinaryExpression && expression.isComaprationWithNull() -> {
|
||||
val comparedExpression =
|
||||
if (expression.left.isNullExpression()) expression.right
|
||||
else expression.left
|
||||
comparedExpression?.addEqualsNullabilityConstraint(Nullability.NULLABLE, ConstraintCameFrom.COMPARED_WITH_NULL)
|
||||
}
|
||||
|
||||
expression is KtBinaryExpression -> {
|
||||
expression.left?.addEqualsNullabilityConstraint(Nullability.NOT_NULL, ConstraintCameFrom.USED_AS_RECEIVER)
|
||||
expression.right?.addEqualsNullabilityConstraint(Nullability.NOT_NULL, ConstraintCameFrom.USED_AS_RECEIVER)
|
||||
}
|
||||
|
||||
expression is KtProperty -> {
|
||||
analysisContext.declarationToTypeVariable[expression]?.also { typeVariable ->
|
||||
expression.initializer?.addSubtypeNullabilityConstraint(
|
||||
typeVariable,
|
||||
ConstraintCameFrom.INITIALIZER
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
expression is KtParameter -> {
|
||||
analysisContext.declarationToTypeVariable[expression]?.also { typeVariable ->
|
||||
expression.defaultValue?.addSubtypeNullabilityConstraint(
|
||||
typeVariable,
|
||||
ConstraintCameFrom.INITIALIZER
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
expression is KtIfExpression -> {
|
||||
expression.condition?.addEqualsNullabilityConstraint(Nullability.NOT_NULL, ConstraintCameFrom.INITIALIZER)
|
||||
}
|
||||
|
||||
expression is KtWhileExpression -> {
|
||||
expression.condition?.addEqualsNullabilityConstraint(Nullability.NOT_NULL, ConstraintCameFrom.INITIALIZER)
|
||||
}
|
||||
|
||||
expression is KtForExpression -> {
|
||||
expression.loopRange?.addEqualsNullabilityConstraint(Nullability.NOT_NULL, ConstraintCameFrom.INITIALIZER)
|
||||
}
|
||||
|
||||
expression is KtCallExpression -> {
|
||||
collectConstraintsForCallExpression(
|
||||
expression,
|
||||
expression.parent?.safeAs<KtQualifiedExpression>()?.receiverExpression
|
||||
)
|
||||
}
|
||||
|
||||
expression is KtReturnExpression -> {
|
||||
val targetTypeVariable = expression.getTargetFunction(expression.analyze())?.let { function ->
|
||||
analysisContext.declarationToTypeVariable[function]
|
||||
}
|
||||
if (targetTypeVariable != null) {
|
||||
expression.returnedExpression?.addSubtypeNullabilityConstraint(
|
||||
targetTypeVariable,
|
||||
ConstraintCameFrom.ASSIGNMENT_TARGET
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
expression is KtNamedFunction -> {
|
||||
collectSuperDeclarationConstraintsForFunction(expression)
|
||||
}
|
||||
}
|
||||
Unit
|
||||
}
|
||||
|
||||
|
||||
private fun collectSuperDeclarationConstraintsForFunction(function: KtNamedFunction) = with(constraintBuilder) {
|
||||
val descriptor = function.resolveToDescriptorIfAny() ?: return
|
||||
val superDeclarationDescriptor = descriptor.firstOverridden { true }
|
||||
?.takeIf { it != descriptor }
|
||||
?.safeAs<FunctionDescriptor>()
|
||||
?: return
|
||||
|
||||
val containingSuperClassBoundType =
|
||||
superDeclarationDescriptor.containingDeclaration.safeAs<ClassDescriptor>()?.let { klass ->
|
||||
GenericBoundType(
|
||||
DescriptorClassReference(klass),
|
||||
emptyList(),//TODO,
|
||||
isNull = false
|
||||
)
|
||||
}
|
||||
|
||||
val superFunctionPsi = superDeclarationDescriptor.findPsi() as? KtNamedFunction
|
||||
|
||||
val superFunctionBoundType =
|
||||
superFunctionPsi?.let { analysisContext.declarationToTypeVariable[it] }?.let {
|
||||
TypeVariableBoundType(it)
|
||||
} ?: descriptor.returnType?.let { type ->
|
||||
boundTypeStorage.boundTypeForType(type, containingSuperClassBoundType, emptyMap()/*TODO*/)
|
||||
} ?: return
|
||||
|
||||
analysisContext.declarationToTypeVariable[function]?.addEqualsNullabilityConstraint(
|
||||
superFunctionBoundType,
|
||||
ConstraintCameFrom.SUPER_DECLARATION
|
||||
)
|
||||
|
||||
for (parameterIndex in function.valueParameters.indices) {
|
||||
val parameterTypeVariable =
|
||||
function.valueParameters[parameterIndex].let { analysisContext.declarationToTypeVariable[it] } ?: return
|
||||
|
||||
val superParameterBoundType = superFunctionPsi?.valueParameters?.get(parameterIndex)
|
||||
?.let { analysisContext.declarationToTypeVariable[it] }
|
||||
?.let { TypeVariableBoundType(it) }
|
||||
?: superDeclarationDescriptor.valueParameters.getOrNull(parameterIndex)
|
||||
?.let { boundTypeStorage.boundTypeForType(it.type, containingSuperClassBoundType, emptyMap()/*TODO*/) }
|
||||
?: continue
|
||||
parameterTypeVariable.addEqualsNullabilityConstraint(superParameterBoundType, ConstraintCameFrom.SUPER_DECLARATION)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun collectConstraintsForCallExpression(callExpression: KtCallExpression, receiver: KtExpression?) = with(constraintBuilder) {
|
||||
val receiverBoundType = receiver?.boundType
|
||||
val descriptor = callExpression.resolveToCall()?.candidateDescriptor?.original?.safeAs<CallableDescriptor>() ?: return
|
||||
val function = descriptor.findPsi()?.safeAs<KtNamedFunction>()
|
||||
|
||||
fun parameterBoundTypeIndex(index: Int): BoundType? =
|
||||
function?.valueParameters?.let { parameters ->
|
||||
val parameter =
|
||||
if (index <= parameters.lastIndex) parameters[index]
|
||||
else parameters.lastOrNull()?.takeIf { parameter ->
|
||||
parameter.isVarArg
|
||||
}
|
||||
val typeVariable = parameter?.let { analysisContext.declarationToTypeVariable[it] }
|
||||
typeVariable?.let {
|
||||
TypeVariableBoundType(it)
|
||||
}
|
||||
} ?: run {
|
||||
if (index < descriptor.valueParameters.lastIndex
|
||||
|| index == descriptor.valueParameters.lastIndex && descriptor.valueParameters.lastOrNull()?.isVararg == false
|
||||
) descriptor.valueParameters[index].type
|
||||
else {
|
||||
descriptor.valueParameters.lastOrNull()?.takeIf { parameter ->
|
||||
parameter.isVararg && KotlinBuiltIns.isArray(parameter.type)
|
||||
}?.let { parameter ->
|
||||
parameter.type.arguments.singleOrNull()?.type
|
||||
}
|
||||
}
|
||||
}?.let { type ->
|
||||
boundTypeStorage
|
||||
.boundTypeForType(type, receiverBoundType, callExpression.typeArgumentsDescriptors(descriptor))
|
||||
}
|
||||
|
||||
|
||||
for (argumentIndex in callExpression.valueArguments.indices) {
|
||||
val argument = callExpression.valueArguments[argumentIndex].getArgumentExpression() ?: continue
|
||||
|
||||
argument.addSubtypeNullabilityConstraint(
|
||||
parameterBoundTypeIndex(argumentIndex) ?: continue,
|
||||
ConstraintCameFrom.PARAMETER_PASSED
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtCallExpression.typeArgumentsDescriptors(descriptor: CallableDescriptor): Map<TypeParameterDescriptor, TypeVariable> =
|
||||
descriptor.typeParameters.zip(typeArguments) { typeParameter, typeArgument ->
|
||||
typeParameter to
|
||||
this@ConstraintsCollector.analysisContext.typeElementToTypeVariable.getValue(typeArgument.typeReference?.typeElement!!)
|
||||
}.toMap()
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.nj2k.nullabilityAnalysis
|
||||
|
||||
import com.intellij.psi.PsiComment
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
internal class ContextCreator(private val getNullability: (KtTypeElement) -> Nullability) {
|
||||
|
||||
private fun KtCallableDeclaration.typeElement(): KtTypeElement? =
|
||||
typeReference?.typeElement
|
||||
|
||||
private fun KtElement.asTypeVariableOwner(): TypeVariableOwner? =
|
||||
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 KtBinaryExpressionWithTypeRHS -> right?.typeElement
|
||||
?.takeIf { KtPsiUtil.isUnsafeCast(this) }
|
||||
?.asTypeVariable()
|
||||
?.let { TypeCastTarget(this, it) }
|
||||
|
||||
is KtCallExpression ->
|
||||
if (typeArguments.isNotEmpty())
|
||||
FunctionCallTypeArgumentTarget(
|
||||
this,
|
||||
typeArguments.map { it.typeReference?.typeElement?.asTypeVariable()!! }
|
||||
)
|
||||
else null
|
||||
else -> null
|
||||
}
|
||||
|
||||
fun createContext(analysisScope: AnalysisScope): AnalysisContext {
|
||||
val typeVariableOwners = analysisScope.flatMap {
|
||||
it.collectDescendantsOfType<KtElement>().mapNotNull { ktElement ->
|
||||
ktElement.asTypeVariableOwner()
|
||||
}
|
||||
}
|
||||
|
||||
val typeElementsToTypeVariables =
|
||||
typeVariableOwners.flatMap {
|
||||
it.innerTypeVariables() + it.allTypeVariables
|
||||
}.associateBy { it.typeElement }
|
||||
|
||||
val declarationToTypeVariable = typeVariableOwners.asSequence()
|
||||
.mapNotNull { owner ->
|
||||
if (owner is DeclarationTypeVariableOwner)
|
||||
owner.target to owner.typeVariable
|
||||
else null
|
||||
}.toMap()
|
||||
return AnalysisContext(typeVariableOwners, typeElementsToTypeVariables, declarationToTypeVariable)
|
||||
}
|
||||
|
||||
private fun KtTypeElement.asTypeVariable(): TypeVariable {
|
||||
val nullability = getNullability(this)
|
||||
val classReference: ClassReference = classReference()
|
||||
val typeParameters: List<TypeVariableTypeParameter> =
|
||||
typeArgumentsAsTypes.mapIndexed { index, typeRef ->
|
||||
typeRef?.typeElement?.asTypeVariable()?.let { typeVariable ->
|
||||
TypeVariableTypeParameterWithTypeParameter(
|
||||
typeVariable,
|
||||
classReference.typeParametersVariance(index)
|
||||
)
|
||||
} ?: TypeVariableStartProjectionTypeParameter
|
||||
|
||||
}
|
||||
return TypeVariable(this, classReference, typeParameters, nullability)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun nullabilityByUndefinedNullabilityComment(typeElement: KtTypeElement): Nullability {
|
||||
val undefinedNullabilityComment =
|
||||
typeElement.parent?.safeAs<KtTypeReference>()?.getUndefinedNullabilityComment()?.also { it.delete() }
|
||||
return when {
|
||||
undefinedNullabilityComment != null -> Nullability.UNKNOWN
|
||||
typeElement is KtNullableType -> Nullability.NULLABLE
|
||||
else -> Nullability.NOT_NULL
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun KtElement.getUndefinedNullabilityComment(): PsiComment? =
|
||||
prevSibling?.safeAs<PsiComment>()?.takeIf { it.text == UNDEFINED_NULLABILITY_COMMENT }
|
||||
?: parent?.safeAs<KtTypeProjection>()?.getUndefinedNullabilityComment()
|
||||
|
||||
|
||||
fun prepareTypeElementByMakingAllTypesNullableConsideringNullabilityComment(typeElement: KtTypeElement) {
|
||||
val hasUndefinedNullabilityComment =
|
||||
typeElement.parent?.safeAs<KtTypeReference>()?.getUndefinedNullabilityComment() != null
|
||||
if (hasUndefinedNullabilityComment) {
|
||||
typeElement.changeNullability(toNullable = true)
|
||||
}
|
||||
}
|
||||
|
||||
fun preapareTypeElementByMakingAllTypesNullable(typeElement: KtTypeElement) {
|
||||
typeElement.changeNullability(toNullable = true)
|
||||
}
|
||||
|
||||
|
||||
internal const val UNDEFINED_NULLABILITY_COMMENT = "/*UNDEFINED*/"
|
||||
|
||||
|
||||
internal data class AnalysisContext(
|
||||
val typeVariableOwners: List<TypeVariableOwner>,
|
||||
val typeElementToTypeVariable: Map<KtTypeElement, TypeVariable>,
|
||||
val declarationToTypeVariable: Map<KtCallableDeclaration, TypeVariable>
|
||||
)
|
||||
|
||||
data class AnalysisScope(val elements: List<KtElement>) : Iterable<KtElement> by elements {
|
||||
constructor(vararg elements: KtElement) : this(elements.toList())
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.nj2k.nullabilityAnalysis
|
||||
|
||||
import com.intellij.openapi.application.runWriteAction
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiComment
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.psi.util.parentOfType
|
||||
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
|
||||
internal fun TypeVariable.changeNullability(toNullable: Boolean) {
|
||||
typeElement.changeNullability(toNullable)
|
||||
}
|
||||
|
||||
internal fun KtTypeElement.changeNullability(toNullable: Boolean) {
|
||||
val factory = KtPsiFactory(this)
|
||||
if (this is KtNullableType && !toNullable) {
|
||||
replace(factory.createType(innerType!!.text).typeElement!!)
|
||||
}
|
||||
if (this !is KtNullableType && toNullable) {
|
||||
replace(factory.createType("$text?").typeElement!!)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun AnalysisContext.fixTypeVariablesNullability() {
|
||||
if (typeElementToTypeVariable.isEmpty()) return
|
||||
|
||||
val deepComparator = Comparator<TypeVariable> { o1, o2 ->
|
||||
if (o1.typeElement.isAncestor(o2.typeElement)) 1 else -1
|
||||
}
|
||||
for (typeVariableOwner in typeVariableOwners) {
|
||||
for (typeVariable in (typeVariableOwner.innerTypeVariables() + typeVariableOwner.allTypeVariables).sortedWith(deepComparator)) {
|
||||
when (typeVariable.nullability) {
|
||||
Nullability.NOT_NULL -> typeVariable.changeNullability(toNullable = false)
|
||||
Nullability.NULLABLE -> typeVariable.changeNullability(toNullable = true)
|
||||
Nullability.UNKNOWN -> {
|
||||
if (typeVariableOwner is FunctionCallTypeArgumentTarget) {
|
||||
typeVariable.changeNullability(toNullable = false)
|
||||
} else {
|
||||
typeVariable.changeNullability(toNullable = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun KtTypeElement.classReference(): ClassReference {
|
||||
val target = when (this) {
|
||||
is KtNullableType -> innerType?.safeAs()
|
||||
is KtUserType -> this
|
||||
else -> null
|
||||
}?.referenceExpression?.resolve()
|
||||
return when (target) {
|
||||
is KtClassOrObject -> KtClassReference(target)
|
||||
is PsiClass -> JavaClassReference(target)
|
||||
is KtTypeAlias -> target.getTypeReference()?.typeElement?.classReference()!!
|
||||
is KtTypeParameter -> TypeParameterClassReference(target)
|
||||
else -> {
|
||||
println("no class ref for `$text`")
|
||||
UnknownClassReference(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NullabilityAnalysisFacade(
|
||||
private val getTypeElementNullability: (KtTypeElement) -> Nullability,
|
||||
private val prepareTypeElement: (KtTypeElement) -> Unit,
|
||||
private val debugPrint: Boolean
|
||||
) {
|
||||
fun fixNullability(analysisScope: AnalysisScope) {
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
runWriteAction {
|
||||
analysisScope.prepareTypeElements(prepareTypeElement)
|
||||
val context = ContextCreator(getTypeElementNullability).createContext(analysisScope)
|
||||
if (debugPrint)
|
||||
with(Printer(context)) {
|
||||
analysisScope.single().addTypeVariablesNames()
|
||||
}
|
||||
|
||||
val constraints = ConstraintsCollector(context, debugPrint).collectConstraints(analysisScope)
|
||||
Solver(context, debugPrint).solveConstraints(constraints)
|
||||
context.fixTypeVariablesNullability()
|
||||
analysisScope.clearUndefinedLabels()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun AnalysisScope.prepareTypeElements(prepareTypeElement: (KtTypeElement) -> Unit) {
|
||||
val typeElements = flatMap { it.collectDescendantsOfType<KtTypeElement>() }
|
||||
typeElements.forEach { typeElement ->
|
||||
if (typeElement.parentOfType<KtSuperTypeCallEntry>() == null) {
|
||||
prepareTypeElement(typeElement)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun AnalysisScope.clearUndefinedLabels() {
|
||||
val comments = mutableListOf<PsiComment>()
|
||||
forEach { element ->
|
||||
element.accept(object : PsiElementVisitor() {
|
||||
override fun visitElement(element: PsiElement) {
|
||||
element.acceptChildren(this)
|
||||
}
|
||||
|
||||
override fun visitComment(comment: PsiComment) {
|
||||
if (comment.text == UNDEFINED_NULLABILITY_COMMENT) {
|
||||
comments += comment
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
comments.forEach { it.delete() }
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.nj2k.nullabilityAnalysis
|
||||
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.psi.KtTypeElement
|
||||
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
|
||||
|
||||
internal class Printer(private val analysisContext: AnalysisContext) {
|
||||
private val namer = Namer(analysisContext)
|
||||
|
||||
internal val TypeVariable.name: String
|
||||
get() = namer.name(this)
|
||||
|
||||
private fun ClassReference.asString() =
|
||||
when (this) {
|
||||
is KtClassReference -> klass.name
|
||||
is JavaClassReference -> klass.name
|
||||
is DescriptorClassReference -> descriptor.name.toString()
|
||||
is TypeParameterClassReference -> typeParameter.name
|
||||
is LiteralClassReference -> "LITERAL"
|
||||
is UnknownClassReference -> text
|
||||
else -> error(this::class.toString())
|
||||
}
|
||||
|
||||
fun BoundType.asString(): String =
|
||||
buildString {
|
||||
if (this@asString is TypeVariableBoundType) {
|
||||
append(this@asString.typeVariable.name)
|
||||
append("@")
|
||||
}
|
||||
append(classReference.asString())
|
||||
if (typeParameters.isNotEmpty()) {
|
||||
typeParameters.joinTo(this, ", ", "<", ">") { it.boundType.asString() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun Constraint.asString() =
|
||||
when (this) {
|
||||
is EqualConstraint -> "${leftBound.asString()} := ${rightBound.asString()}"
|
||||
is SubtypeConstraint -> "${lowerBound.asString()} <: ${upperBound.asString()}"
|
||||
else -> error("Unknown constraint ${this::class.qualifiedName}")
|
||||
} + ", because of '$cameFrom'"
|
||||
|
||||
|
||||
private fun ConstraintBound.asString(): String =
|
||||
when (this) {
|
||||
is LiteralBound -> nullability.toString()
|
||||
is TypeVariableBound -> typeVariable.name
|
||||
else -> error("Unknown constraint bound ${this::class.qualifiedName}")
|
||||
}
|
||||
|
||||
|
||||
internal fun KtElement.addTypeVariablesNames() {
|
||||
val factory = KtPsiFactory(this)
|
||||
for (typeElement in collectDescendantsOfType<KtTypeElement>()) {
|
||||
val typeVariableName = this@Printer.analysisContext.typeElementToTypeVariable[typeElement]?.name ?: continue
|
||||
val comment = factory.createComment("/*$typeVariableName@*/")
|
||||
typeElement.parent.addBefore(comment, typeElement)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun List<Constraint>.listConstrains() =
|
||||
joinToString(separator = "\n") { it.asString() }
|
||||
}
|
||||
|
||||
|
||||
private class Namer(analysisContext: AnalysisContext) {
|
||||
val names = analysisContext.typeElementToTypeVariable.values.mapIndexed { index, typeVariable ->
|
||||
typeVariable to "T$index"
|
||||
}.toMap()
|
||||
|
||||
fun name(typeVariable: TypeVariable): String =
|
||||
names.getValue(typeVariable)
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.nj2k.nullabilityAnalysis
|
||||
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
|
||||
internal class Solver(
|
||||
private val analysisContext: AnalysisContext,
|
||||
private val printConstraints: Boolean
|
||||
) {
|
||||
private val printer = Printer(analysisContext)
|
||||
|
||||
private fun List<Constraint>.printDebugInfo(step: Int) =
|
||||
with(printer) {
|
||||
if (printConstraints) {
|
||||
println("Step $step:")
|
||||
println(listConstrains())
|
||||
println()
|
||||
println("type variables:")
|
||||
for (typeVariable in analysisContext.typeElementToTypeVariable.values) {
|
||||
println("${typeVariable.name} := ${typeVariable.nullability}")
|
||||
}
|
||||
println("---------------\n")
|
||||
}
|
||||
}
|
||||
|
||||
fun solveConstraints(constraints: List<Constraint>) {
|
||||
val constraints = constraints.toMutableList()
|
||||
var currentStep = ConstraintCameFrom.values().first()
|
||||
|
||||
var i = 0
|
||||
do {
|
||||
var somethingChanged = false
|
||||
with(constraints) {
|
||||
printDebugInfo(i)
|
||||
somethingChanged = handleConstraintsWithNullableLowerBound(currentStep) || somethingChanged
|
||||
somethingChanged = handleConstraintsWithNotNullUpperBound(currentStep) || somethingChanged
|
||||
somethingChanged = handleEqualConstraints(currentStep) || somethingChanged
|
||||
somethingChanged = substituteConstraints() || somethingChanged
|
||||
cleanConstraints()
|
||||
}
|
||||
|
||||
|
||||
if (!somethingChanged) {
|
||||
if (currentStep.ordinal < ConstraintCameFrom.values().lastIndex) {
|
||||
currentStep = ConstraintCameFrom.values()[currentStep.ordinal + 1]
|
||||
somethingChanged = true
|
||||
}
|
||||
}
|
||||
if (!somethingChanged) {
|
||||
val typeVariable = constraints.getTypeVariableAsEqualsOrUpperBound()
|
||||
if (typeVariable != null) {
|
||||
typeVariable.setNullabilityIfNotFixed(Nullability.NOT_NULL)
|
||||
somethingChanged = true
|
||||
}
|
||||
}
|
||||
i++
|
||||
} while (somethingChanged)
|
||||
}
|
||||
|
||||
|
||||
private fun MutableList<Constraint>.cleanConstraints() {
|
||||
val newConstraints =
|
||||
distinct()
|
||||
.filterNot { constraint ->
|
||||
constraint is SubtypeConstraint
|
||||
&& constraint.lowerBound is LiteralBound
|
||||
&& constraint.upperBound is LiteralBound
|
||||
}
|
||||
|
||||
if (newConstraints.size < size) {
|
||||
clear()
|
||||
addAll(newConstraints)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<Constraint>.handleConstraintsWithNullableLowerBound(step: ConstraintCameFrom): Boolean {
|
||||
var somethingChanged = false
|
||||
val nullableConstraints = getConstraintsWithNullableLowerBound(step)
|
||||
if (nullableConstraints.isNotEmpty()) {
|
||||
this -= nullableConstraints
|
||||
for ((_, upperBound) in nullableConstraints) {
|
||||
if (upperBound is TypeVariableBound) {
|
||||
somethingChanged = true
|
||||
upperBound.typeVariable.setNullabilityIfNotFixed(Nullability.NULLABLE)
|
||||
}
|
||||
}
|
||||
}
|
||||
return somethingChanged
|
||||
}
|
||||
|
||||
private fun MutableList<Constraint>.handleConstraintsWithNotNullUpperBound(step: ConstraintCameFrom): Boolean {
|
||||
var somethingChanged = false
|
||||
val nullableConstraints = getConstraintsWithNotNullUpperBound(step)
|
||||
if (nullableConstraints.isNotEmpty()) {
|
||||
this -= nullableConstraints
|
||||
for ((lowerBound, _) in nullableConstraints) {
|
||||
if (lowerBound is TypeVariableBound) {
|
||||
somethingChanged = true
|
||||
lowerBound.typeVariable.setNullabilityIfNotFixed(Nullability.NOT_NULL)
|
||||
}
|
||||
}
|
||||
}
|
||||
return somethingChanged
|
||||
}
|
||||
|
||||
private fun ConstraintBound.fixedNullability(): Nullability? =
|
||||
when {
|
||||
this is LiteralBound -> nullability
|
||||
this is TypeVariableBound && typeVariable.isFixed -> typeVariable.nullability
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun MutableList<Constraint>.handleEqualConstraints(step: ConstraintCameFrom): Boolean {
|
||||
var somethingChanged = false
|
||||
val equalsConstraints = filterIsInstance<EqualConstraint>().filter { it.cameFrom <= step }
|
||||
if (equalsConstraints.isNotEmpty()) {
|
||||
for (constraint in equalsConstraints) {
|
||||
val (leftBound, rightBound) = constraint
|
||||
when {
|
||||
leftBound is TypeVariableBound && rightBound.fixedNullability() != null -> {
|
||||
this -= constraint
|
||||
somethingChanged = true
|
||||
leftBound.typeVariable.setNullabilityIfNotFixed(rightBound.fixedNullability()!!)
|
||||
}
|
||||
|
||||
rightBound is TypeVariableBound && leftBound.fixedNullability() != null -> {
|
||||
this -= constraint
|
||||
somethingChanged = true
|
||||
rightBound.typeVariable.setNullabilityIfNotFixed(leftBound.fixedNullability()!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return somethingChanged
|
||||
}
|
||||
|
||||
|
||||
private fun List<Constraint>.substituteConstraints(): Boolean {
|
||||
var somethingChanged = false
|
||||
for (constraint in this) {
|
||||
if (constraint is SubtypeConstraint) {
|
||||
val (lower, upper) = constraint
|
||||
if (lower is TypeVariableBound && lower.typeVariable.isFixed) {
|
||||
somethingChanged = true
|
||||
constraint.lowerBound = LiteralBound(lower.typeVariable.nullability)
|
||||
}
|
||||
if (upper is TypeVariableBound && upper.typeVariable.isFixed) {
|
||||
somethingChanged = true
|
||||
constraint.upperBound = LiteralBound(upper.typeVariable.nullability)
|
||||
}
|
||||
}
|
||||
}
|
||||
return somethingChanged
|
||||
}
|
||||
|
||||
private fun List<Constraint>.getConstraintsWithNullableLowerBound(cameFrom: ConstraintCameFrom) =
|
||||
filterIsInstance<SubtypeConstraint>().filter { constraint ->
|
||||
constraint.cameFrom <= cameFrom
|
||||
&& constraint.lowerBound.safeAs<LiteralBound>()?.nullability == Nullability.NULLABLE
|
||||
}
|
||||
|
||||
private fun List<Constraint>.getConstraintsWithNotNullUpperBound(cameFrom: ConstraintCameFrom) =
|
||||
filterIsInstance<SubtypeConstraint>().filter { constraint ->
|
||||
constraint.cameFrom <= cameFrom
|
||||
&& constraint.upperBound.safeAs<LiteralBound>()?.nullability == Nullability.NOT_NULL
|
||||
}
|
||||
|
||||
|
||||
private fun List<Constraint>.getTypeVariableAsEqualsOrUpperBound(): TypeVariable? =
|
||||
asSequence().filterIsInstance<SubtypeConstraint>()
|
||||
.map { it.upperBound }
|
||||
.firstIsInstanceOrNull<TypeVariableBound>()
|
||||
?.typeVariable
|
||||
?: asSequence().filterIsInstance<EqualConstraint>()
|
||||
.flatMap { sequenceOf(it.leftBound, it.rightBound) }
|
||||
.firstIsInstanceOrNull<TypeVariableBound>()
|
||||
?.typeVariable
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.nj2k.nullabilityAnalysis
|
||||
|
||||
import com.intellij.psi.tree.IElementType
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
|
||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactoryImpl
|
||||
import org.jetbrains.kotlin.types.isNullable
|
||||
import org.jetbrains.kotlin.util.javaslang.getOrNull
|
||||
|
||||
internal inline fun KtExpression.deepestReceiver(): KtExpression =
|
||||
generateSequence(this) {
|
||||
if (it is KtQualifiedExpression) it.receiverExpression else null
|
||||
}.last()
|
||||
|
||||
internal inline fun KtExpression.isNullable(): Boolean =
|
||||
getType(analyze())?.isNullable() != false
|
||||
|
||||
|
||||
internal inline fun KtExpression.getForcedNullability(): Nullability? {
|
||||
val bindingContext = analyze()
|
||||
val type = this.getType(bindingContext) ?: return null
|
||||
if (!type.isNullable()) return Nullability.NOT_NULL
|
||||
|
||||
//TODO better way of getting DataFlowValueFactoryImpl
|
||||
val dataInfo = DataFlowValueFactoryImpl(LanguageVersionSettingsImpl.DEFAULT)
|
||||
.createDataFlowValue(
|
||||
this,
|
||||
type,
|
||||
bindingContext,
|
||||
getResolutionFacade().moduleDescriptor
|
||||
)
|
||||
val nullability =
|
||||
analyze()[BindingContext.EXPRESSION_TYPE_INFO, this]?.dataFlowInfo?.completeNullabilityInfo?.get(dataInfo)?.getOrNull()
|
||||
return if (nullability == org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability.NOT_NULL) {
|
||||
Nullability.NOT_NULL
|
||||
} else null
|
||||
|
||||
}
|
||||
|
||||
|
||||
private fun IElementType.isEqualsToken() =
|
||||
this == KtTokens.EQEQ
|
||||
|| this == KtTokens.EXCLEQ
|
||||
|| this == KtTokens.EQEQEQ
|
||||
|| this == KtTokens.EXCLEQEQEQ
|
||||
|
||||
fun KtBinaryExpression.isComaprationWithNull() =
|
||||
operationToken.isEqualsToken() &&
|
||||
(left.isNullExpression() || right.isNullExpression())
|
||||
|
||||
fun KtExpression.isLiteral() =
|
||||
this is KtStringTemplateExpression
|
||||
|| this is KtLiteralStringTemplateEntry
|
||||
|| this is KtConstantExpression
|
||||
|| isNullExpression()
|
||||
Reference in New Issue
Block a user