diff --git a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/J2kPostProcessor.kt b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/J2kPostProcessor.kt index 589e8f7f11b..8bfb0826639 100644 --- a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/J2kPostProcessor.kt +++ b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/J2kPostProcessor.kt @@ -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) diff --git a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/BoundTypeStorage.kt b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/BoundTypeStorage.kt new file mode 100644 index 00000000000..3a9bddf590d --- /dev/null +++ b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/BoundTypeStorage.kt @@ -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() + 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 + ): BoundType? = + type.toBoundType(contextBoundType, typeParameterDescriptors) + + private fun KtExpression.resolveToTypeVariable(): TypeVariable? = + getCalleeExpressionIfAny() + ?.safeAs() + ?.resolve() + ?.safeAs() + ?.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() + ?.resolve() + ?.safeAs() + ?.typeReference + ?.typeElement + typeElement?.let { analysisAnalysisContext.typeElementToTypeVariable[it] }?.also { + return TypeVariableBoundType(it) + } + val bindingContext = analyze() + val descriptor = + getResolvedCall(bindingContext)?.candidateDescriptor?.original?.safeAs() ?: 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() + ?.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 + ): 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() + } +} \ No newline at end of file diff --git a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/ConstraintBuilder.kt b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/ConstraintBuilder.kt new file mode 100644 index 00000000000..9d890287f3d --- /dev/null +++ b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/ConstraintBuilder.kt @@ -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() + + internal fun getConstraints(): List = 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() + ?.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) + } +} \ No newline at end of file diff --git a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/constraints.kt b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/constraints.kt new file mode 100644 index 00000000000..bfbfa480d48 --- /dev/null +++ b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/constraints.kt @@ -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 +} + +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 +) : 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 + 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 = + 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, + 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, + 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 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 = + when (this) { + is SingleTypeVariableOwner -> typeVariable.innerTypeVariables() + is MultipleTypeVariablesOwner -> typeVariables.flatMap { it.innerTypeVariables() } + else -> error("") + } + +internal fun TypeVariable.innerTypeVariables(): List = + typeParameters.flatMap { typeParameter -> + typeParameter.safeAs()?.let { typeVariableTypeParameter -> + typeVariableTypeParameter.typeVariable.innerTypeVariables() + listOfNotNull(typeVariableTypeParameter.typeVariable) + }.orEmpty() + } \ No newline at end of file diff --git a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/constraintsCollector.kt b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/constraintsCollector.kt new file mode 100644 index 00000000000..0b57507eed4 --- /dev/null +++ b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/constraintsCollector.kt @@ -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 { + analysisScope.forEach { element -> + element.forEachDescendantOfType { 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()?.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() + ?: return + + val containingSuperClassBoundType = + superDeclarationDescriptor.containingDeclaration.safeAs()?.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() ?: return + val function = descriptor.findPsi()?.safeAs() + + 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 = + descriptor.typeParameters.zip(typeArguments) { typeParameter, typeArgument -> + typeParameter to + this@ConstraintsCollector.analysisContext.typeElementToTypeVariable.getValue(typeArgument.typeReference?.typeElement!!) + }.toMap() +} \ No newline at end of file diff --git a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/context.kt b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/context.kt new file mode 100644 index 00000000000..5223073b77c --- /dev/null +++ b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/context.kt @@ -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().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 = + 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()?.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()?.takeIf { it.text == UNDEFINED_NULLABILITY_COMMENT } + ?: parent?.safeAs()?.getUndefinedNullabilityComment() + + +fun prepareTypeElementByMakingAllTypesNullableConsideringNullabilityComment(typeElement: KtTypeElement) { + val hasUndefinedNullabilityComment = + typeElement.parent?.safeAs()?.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, + val typeElementToTypeVariable: Map, + val declarationToTypeVariable: Map +) + +data class AnalysisScope(val elements: List) : Iterable by elements { + constructor(vararg elements: KtElement) : this(elements.toList()) +} diff --git a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/nullabilityAnalysis.kt b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/nullabilityAnalysis.kt new file mode 100644 index 00000000000..cce1abf55c1 --- /dev/null +++ b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/nullabilityAnalysis.kt @@ -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 { 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() } + typeElements.forEach { typeElement -> + if (typeElement.parentOfType() == null) { + prepareTypeElement(typeElement) + } + } +} + + +private fun AnalysisScope.clearUndefinedLabels() { + val comments = mutableListOf() + 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() } +} + + diff --git a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/printing.kt b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/printing.kt new file mode 100644 index 00000000000..77490d86db2 --- /dev/null +++ b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/printing.kt @@ -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()) { + val typeVariableName = this@Printer.analysisContext.typeElementToTypeVariable[typeElement]?.name ?: continue + val comment = factory.createComment("/*$typeVariableName@*/") + typeElement.parent.addBefore(comment, typeElement) + } + } + + internal fun List.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) +} \ No newline at end of file diff --git a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/solver.kt b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/solver.kt new file mode 100644 index 00000000000..160693b6191 --- /dev/null +++ b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/solver.kt @@ -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.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) { + 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.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.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.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.handleEqualConstraints(step: ConstraintCameFrom): Boolean { + var somethingChanged = false + val equalsConstraints = filterIsInstance().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.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.getConstraintsWithNullableLowerBound(cameFrom: ConstraintCameFrom) = + filterIsInstance().filter { constraint -> + constraint.cameFrom <= cameFrom + && constraint.lowerBound.safeAs()?.nullability == Nullability.NULLABLE + } + + private fun List.getConstraintsWithNotNullUpperBound(cameFrom: ConstraintCameFrom) = + filterIsInstance().filter { constraint -> + constraint.cameFrom <= cameFrom + && constraint.upperBound.safeAs()?.nullability == Nullability.NOT_NULL + } + + + private fun List.getTypeVariableAsEqualsOrUpperBound(): TypeVariable? = + asSequence().filterIsInstance() + .map { it.upperBound } + .firstIsInstanceOrNull() + ?.typeVariable + ?: asSequence().filterIsInstance() + .flatMap { sequenceOf(it.leftBound, it.rightBound) } + .firstIsInstanceOrNull() + ?.typeVariable + +} + diff --git a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/utils.kt b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/utils.kt new file mode 100644 index 00000000000..305b681d840 --- /dev/null +++ b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/utils.kt @@ -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() \ No newline at end of file