New J2K: Add nullability analysis to post processing

This commit is contained in:
Ilya Kirillov
2019-02-25 17:45:52 +03:00
committed by Ilya Kirillov
parent 63d3f90373
commit b6625c536d
10 changed files with 1286 additions and 8 deletions
@@ -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)
@@ -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()
}
}
@@ -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()
}
@@ -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())
}
@@ -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()