diff --git a/j2k/src/org/jetbrains/jet/j2k/TypeConverter.kt b/j2k/src/org/jetbrains/jet/j2k/TypeConverter.kt index 320eaa0a387..42920634644 100644 --- a/j2k/src/org/jetbrains/jet/j2k/TypeConverter.kt +++ b/j2k/src/org/jetbrains/jet/j2k/TypeConverter.kt @@ -19,10 +19,6 @@ package org.jetbrains.jet.j2k import org.jetbrains.jet.j2k.ast.Type import org.jetbrains.jet.j2k.ast.Nullability import com.intellij.psi.* -import java.util.HashMap -import java.util.HashSet -import org.jetbrains.jet.j2k.ast.Import -import org.jetbrains.jet.j2k.ast.ImportList import org.jetbrains.jet.j2k.ast.assignPrototype import com.intellij.psi.CommonClassNames.JAVA_LANG_OBJECT import org.jetbrains.jet.j2k.ast.assignNoPrototype @@ -32,10 +28,9 @@ import org.jetbrains.jet.j2k.ast.ArrayType import org.jetbrains.jet.j2k.ast.ClassType import org.jetbrains.jet.j2k.ast.ReferenceElement import org.jetbrains.jet.j2k.ast.Identifier +import java.util.HashMap class TypeConverter(val converter: Converter) { - private val nullabilityCache = HashMap() - public fun convertType(type: PsiType?, nullability: Nullability = Nullability.Default): Type { if (type == null) return ErrorType().assignNoPrototype() @@ -62,162 +57,16 @@ class TypeConverter(val converter: Converter) { return result.assignPrototype(variable.getTypeElement()) } - public fun variableNullability(variable: PsiVariable): Nullability { - val cached = nullabilityCache[variable] - if (cached != null) return cached - val value = variableNullabilityNoCache(variable) - nullabilityCache[variable] = value - return value - } - - private fun variableNullabilityNoCache(variable: PsiVariable): Nullability { - if (variable is PsiEnumConstant) return Nullability.NotNull - val variableType = variable.getType() - if (variableType is PsiPrimitiveType) return Nullability.NotNull - - var nullability = variable.nullabilityFromAnnotations() - - if (nullability == Nullability.Default && variable is PsiParameter) { - val scope = variable.getDeclarationScope() - if (scope is PsiMethod) { - val paramIndex = scope.getParameterList().getParameters().indexOf(variable) - assert(paramIndex >= 0) - val superSignatures = scope.getHierarchicalMethodSignature().getSuperSignatures() - nullability = superSignatures.map { signature -> - val params = signature.getMethod().getParameterList().getParameters() - if (paramIndex < params.size) variableNullability(params[paramIndex]) else Nullability.Default - }.firstOrNull { it != Nullability.Default } ?: Nullability.Default - } - } - - if (nullability == Nullability.Default) { - val initializer = variable.getInitializer() - if (initializer != null) { - val initializerNullability = initializer.nullability() - if (variable.isEffectivelyFinal()) { - nullability = initializerNullability - } - else if (initializerNullability == Nullability.Nullable) { // if variable is not final then non-nullability of initializer does not mean that variable is non-null - nullability = Nullability.Nullable - } - } - } - - // variables of types like Integer are most likely nullable - if (nullability == Nullability.Default && variableType.getCanonicalText() in boxingTypes) { - return Nullability.Nullable - } - - if (nullability == Nullability.Default && variable.isMainMethodParameter() ) { - return Nullability.NotNull - } - - if (!converter.conversionScope.contains(variable)) { // do not analyze usages out of our conversion scope - if (variable is PsiParameter) { - // Object.equals corresponds to Any.equals which has nullable parameter: - val scope = variable.getDeclarationScope() - if (scope is PsiMethod && scope.getName() == "equals" && scope.getContainingClass()?.getQualifiedName() == JAVA_LANG_OBJECT) { - return Nullability.Nullable - } - } - - return nullability - } - - if (nullability == Nullability.Default) { - if (variable is PsiField && variable.hasModifierProperty(PsiModifier.PRIVATE) && shouldGenerateDefaultInitializer(converter.referenceSearcher, variable)) { - return Nullability.Nullable - } - } - - if (nullability == Nullability.Default) { - val scope = searchScope(variable) - if (scope != null) { - if (converter.referenceSearcher.findVariableUsages(variable, scope).any { isNullableFromUsage(it) }) { - nullability = Nullability.Nullable - } - } - } - - if (nullability == Nullability.Default && variable is PsiParameter) { - val method = variable.getDeclarationScope() as? PsiMethod - if (method != null) { - val scope = searchScope(method) - if (scope != null) { - val parameters = method.getParameterList().getParameters() - val parameterIndex = parameters.indexOf(variable) - for (call in converter.referenceSearcher.findMethodCalls(method, scope)) { - val args = call.getArgumentList().getExpressions() - if (args.size == parameters.size) { - if (args[parameterIndex].nullability() == Nullability.Nullable) { - nullability = Nullability.Nullable - break - } - } - } - } - } - } - - return nullability - } - - private fun PsiVariable.isMainMethodParameter() = this is PsiParameter && (getDeclarationScope() as? PsiMethod)?.isMainMethod() ?: false - public fun convertMethodReturnType(method: PsiMethod): Type = convertType(method.getReturnType(), methodNullability(method)).assignPrototype(method.getReturnTypeElement()) - public fun methodNullability(method: PsiMethod): Nullability { - val cached = nullabilityCache[method] - if (cached != null) return cached - val value = methodNullabilityNoCache(method) - nullabilityCache[method] = value - return value - } + public fun variableNullability(variable: PsiVariable): Nullability + = nullabilityFlavor.forVariableType(variable) - private fun methodNullabilityNoCache(method: PsiMethod): Nullability { - val returnType = method.getReturnType() - if (returnType is PsiPrimitiveType) return Nullability.NotNull + public fun methodNullability(method: PsiMethod): Nullability + = nullabilityFlavor.forMethodReturnType(method) - var nullability = method.nullabilityFromAnnotations() - - if (nullability == Nullability.Default) { - val superSignatures = method.getHierarchicalMethodSignature().getSuperSignatures() - nullability = superSignatures.map { methodNullability(it.getMethod()) }.firstOrNull { it != Nullability.Default } ?: Nullability.Default - } - - // methods of types like Integer are most likely nullable - if (nullability == Nullability.Default && returnType?.getCanonicalText() in boxingTypes) { - return Nullability.Nullable - } - - if (!converter.conversionScope.contains(method)) return nullability // do not analyze body and usages of methods out of our conversion scope - - if (nullability == Nullability.Default) { - method.getBody()?.accept(object: JavaRecursiveElementVisitor() { - override fun visitReturnStatement(statement: PsiReturnStatement) { - if (statement.getReturnValue()?.nullability() == Nullability.Nullable) { - nullability = Nullability.Nullable - } - } - - override fun visitMethod(method: PsiMethod) { - // do not go inside any other method (e.g. in anonymous class) - } - }) - } - - if (nullability == Nullability.Default) { - val scope = searchScope(method) - if (scope != null) { - if (converter.referenceSearcher.findMethodCalls(method, scope).any { isNullableFromUsage(it) }) { - nullability = Nullability.Nullable - } - } - } - - return nullability - } + private fun PsiVariable.isMainMethodParameter() = this is PsiParameter && (getDeclarationScope() as? PsiMethod)?.isMainMethod() ?: false private fun searchScope(element: PsiElement): PsiElement? { return when(element) { @@ -229,48 +78,6 @@ class TypeConverter(val converter: Converter) { } } - private fun PsiExpression.nullability(): Nullability { - return when (this) { - is PsiLiteralExpression -> if (getType() != PsiType.NULL) Nullability.NotNull else Nullability.Nullable - - is PsiNewExpression -> Nullability.NotNull - - is PsiConditionalExpression -> { - val nullability1 = getThenExpression()?.nullability() - if (nullability1 == Nullability.Nullable) return Nullability.Nullable - val nullability2 = getElseExpression()?.nullability() - if (nullability2 == Nullability.Nullable) return Nullability.Nullable - if (nullability1 == Nullability.NotNull && nullability2 == Nullability.NotNull) return Nullability.NotNull - Nullability.Default - } - - is PsiParenthesizedExpression -> getExpression()?.nullability() ?: Nullability.Default - - - //TODO: some other cases - - else -> Nullability.Default - } - } - - private fun isNullableFromUsage(usage: PsiExpression): Boolean { - val parent = usage.getParent() ?: return false - if (parent is PsiAssignmentExpression && parent.getOperationTokenType() == JavaTokenType.EQ && usage == parent.getLExpression()) { - return parent.getRExpression()?.nullability() == Nullability.Nullable - } - else if (parent is PsiBinaryExpression) { - val operationType = parent.getOperationTokenType() - if (operationType == JavaTokenType.EQEQ || operationType == JavaTokenType.NE) { - val otherOperand = if (usage == parent.getLOperand()) parent.getROperand() else parent.getLOperand() - return otherOperand is PsiLiteralExpression && otherOperand.getType() == PsiType.NULL - } - } - else if (parent is PsiVariable && usage == parent.getInitializer() && parent.isEffectivelyFinal()) { - return variableNullability(parent) == Nullability.Nullable - } - return false - } - private fun PsiVariable.isEffectivelyFinal(): Boolean { if (hasModifierProperty(PsiModifier.FINAL)) return true return when(this) { @@ -280,14 +87,250 @@ class TypeConverter(val converter: Converter) { } } - private fun PsiModifierListOwner.nullabilityFromAnnotations(): Nullability { - val manager = NullableNotNullManager.getInstance(getProject()) - return if (manager.isNotNull(this, false/* we do not check bases because they are checked by callers of this method*/)) - Nullability.NotNull - else if (manager.isNullable(this, false)) - Nullability.Nullable - else - Nullability.Default + private abstract inner class TypeFlavor(val default: T) { + private val cache = HashMap() + + open val forEnumConstant: T = default + open fun fromType(type: PsiType): T = default + open fun fromAnnotations(owner: PsiModifierListOwner): T = default + open fun fromTypeHeuristics(type: PsiType): T = default + open fun forVariableTypeBeforeUsageSearch(variable: PsiVariable): T = default + abstract fun fromUsage(usage: PsiExpression): T + open fun forVariableTypeAfterUsageSearch(variable: PsiVariable): T = default + open fun fromMethodBody(body: PsiCodeBlock): T = default + + fun forVariableType(variable: PsiVariable): T { + val cached = cache[variable] + if (cached != null) return cached + val value = forVariableTypeNoCache(variable) + cache[variable] = value + return value + } + + private fun forVariableTypeNoCache(variable: PsiVariable): T { + if (variable is PsiEnumConstant) return forEnumConstant + + val variableType = variable.getType() + var value = fromType(variableType) + if (value != default) return value + + value = fromAnnotations(variable) + if (value != default) return value + + if (variable is PsiParameter) { + val scope = variable.getDeclarationScope() + if (scope is PsiMethod) { + val paramIndex = scope.getParameterList().getParameters().indexOf(variable) + assert(paramIndex >= 0) + val superSignatures = scope.getHierarchicalMethodSignature().getSuperSignatures() + value = superSignatures.map { signature -> + val params = signature.getMethod().getParameterList().getParameters() + if (paramIndex < params.size) forVariableType(params[paramIndex]) else default + }.firstOrNull { it != default } ?: default + if (value != default) return value + } + } + + value = forVariableTypeBeforeUsageSearch(variable) + if (value != default) return value + + value = fromTypeHeuristics(variableType) + if (value != default) return value + + if (!converter.conversionScope.contains(variable)) return default // do not analyze usages of variables not in our conversion scope + + val scope = searchScope(variable) + if (scope != null) { + value = converter.referenceSearcher.findVariableUsages(variable, scope).map { fromUsage(it) }.firstOrNull { it != default } ?: default + if (value != default) return value + } + + value = forVariableTypeAfterUsageSearch(variable) + if (value != default) return value + + return default + } + + fun forMethodReturnType(method: PsiMethod): T { + val cached = cache[method] + if (cached != null) return cached + val value = forMethodReturnTypeNoCache(method) + cache[method] = value + return value + } + + private fun forMethodReturnTypeNoCache(method: PsiMethod): T { + val returnType = method.getReturnType() ?: return default + + var value = fromType(returnType) + if (value != default) return value + + value = fromAnnotations(method) + if (value != default) return value + + val superSignatures = method.getHierarchicalMethodSignature().getSuperSignatures() + value = superSignatures.map { forMethodReturnType(it.getMethod()) }.firstOrNull { it != default } ?: default + if (value != default) return value + + value = fromTypeHeuristics(returnType) + if (value != default) return value + + if (!converter.conversionScope.contains(method)) return default // do not analyze body and usages of methods out of our conversion scope + + val body = method.getBody() + if (body != null) { + value = fromMethodBody(body) + if (value != default) return value + } + + val scope = searchScope(method) + if (scope != null) { + value = converter.referenceSearcher.findMethodCalls(method, scope).map { fromUsage(it) }.firstOrNull { it != default } ?: default + if (value != default) return value + } + + return default + } + } + + private val nullabilityFlavor = object : TypeFlavor(Nullability.Default) { + override val forEnumConstant: Nullability + get() = Nullability.NotNull + + override fun fromType(type: PsiType) = if (type is PsiPrimitiveType) Nullability.NotNull else Nullability.Default + + override fun fromAnnotations(owner: PsiModifierListOwner): Nullability { + val manager = NullableNotNullManager.getInstance(owner.getProject()) + return if (manager.isNotNull(owner, false/* we do not check bases because they are checked by callers of this method*/)) + Nullability.NotNull + else if (manager.isNullable(owner, false)) + Nullability.Nullable + else + Nullability.Default + } + + override fun forVariableTypeBeforeUsageSearch(variable: PsiVariable): Nullability { + val initializer = variable.getInitializer() + if (initializer != null) { + val initializerNullability = initializer.nullability() + if (initializerNullability != Nullability.Default) { + if (variable.isEffectivelyFinal()) { + return initializerNullability + } + else if (initializerNullability == Nullability.Nullable) { // if variable is not final then non-nullability of initializer does not mean that variable is non-null + return Nullability.Nullable + } + } + } + + if (variable.isMainMethodParameter() ) { + return Nullability.NotNull + } + + if (variable is PsiParameter) { + // Object.equals corresponds to Any.equals which has nullable parameter: + val scope = variable.getDeclarationScope() + if (scope is PsiMethod && scope.getName() == "equals" && scope.getContainingClass()?.getQualifiedName() == JAVA_LANG_OBJECT) { + return Nullability.Nullable + } + } + + if (variable is PsiField + && variable.hasModifierProperty(PsiModifier.PRIVATE) + && converter.conversionScope.contains(variable) + && shouldGenerateDefaultInitializer(converter.referenceSearcher, variable)) { + return Nullability.Nullable + } + + return Nullability.Default + } + + // variables of types like Integer are most likely nullable + override fun fromTypeHeuristics(type: PsiType) = if (type.getCanonicalText() in boxingTypes) Nullability.Nullable else Nullability.Default + + override fun fromUsage(usage: PsiExpression): Nullability { + return if (isNullableFromUsage(usage)) Nullability.Nullable else Nullability.Default + } + + private fun isNullableFromUsage(usage: PsiExpression): Boolean { + val parent = usage.getParent() ?: return false + if (parent is PsiAssignmentExpression && parent.getOperationTokenType() == JavaTokenType.EQ && usage == parent.getLExpression()) { + return parent.getRExpression()?.nullability() == Nullability.Nullable + } + else if (parent is PsiBinaryExpression) { + val operationType = parent.getOperationTokenType() + if (operationType == JavaTokenType.EQEQ || operationType == JavaTokenType.NE) { + val otherOperand = if (usage == parent.getLOperand()) parent.getROperand() else parent.getLOperand() + return otherOperand is PsiLiteralExpression && otherOperand.getType() == PsiType.NULL + } + } + else if (parent is PsiVariable && usage == parent.getInitializer() && parent.isEffectivelyFinal()) { + return variableNullability(parent) == Nullability.Nullable + } + return false + } + + override fun forVariableTypeAfterUsageSearch(variable: PsiVariable): Nullability { + if (variable is PsiParameter) { + val method = variable.getDeclarationScope() as? PsiMethod + if (method != null) { + val scope = searchScope(method) + if (scope != null) { + val parameters = method.getParameterList().getParameters() + val parameterIndex = parameters.indexOf(variable) + for (call in converter.referenceSearcher.findMethodCalls(method, scope)) { + val args = call.getArgumentList().getExpressions() + if (args.size == parameters.size) { + if (args[parameterIndex].nullability() == Nullability.Nullable) { + return Nullability.Nullable + } + } + } + } + } + } + return Nullability.Default + } + + override fun fromMethodBody(body: PsiCodeBlock): Nullability { + var isNullable = false + body.accept(object: JavaRecursiveElementVisitor() { + override fun visitReturnStatement(statement: PsiReturnStatement) { + if (statement.getReturnValue()?.nullability() == Nullability.Nullable) { + isNullable = true + } + } + + override fun visitMethod(method: PsiMethod) { + // do not go inside any other method (e.g. in anonymous class) + } + }) + return if (isNullable) Nullability.Nullable else Nullability.Default + } + + private fun PsiExpression.nullability(): Nullability { + return when (this) { + is PsiLiteralExpression -> if (getType() != PsiType.NULL) Nullability.NotNull else Nullability.Nullable + + is PsiNewExpression -> Nullability.NotNull + + is PsiConditionalExpression -> { + val nullability1 = getThenExpression()?.nullability() + if (nullability1 == Nullability.Nullable) return Nullability.Nullable + val nullability2 = getElseExpression()?.nullability() + if (nullability2 == Nullability.Nullable) return Nullability.Nullable + if (nullability1 == Nullability.NotNull && nullability2 == Nullability.NotNull) return Nullability.NotNull + Nullability.Default + } + + is PsiParenthesizedExpression -> getExpression()?.nullability() ?: Nullability.Default + + + //TODO: some other cases + + else -> Nullability.Default + } + } } class object { diff --git a/j2k/src/org/jetbrains/jet/j2k/ast/Types.kt b/j2k/src/org/jetbrains/jet/j2k/ast/Types.kt index 34b85a7b580..ced6c9dc500 100644 --- a/j2k/src/org/jetbrains/jet/j2k/ast/Types.kt +++ b/j2k/src/org/jetbrains/jet/j2k/ast/Types.kt @@ -32,6 +32,18 @@ fun Nullability.isNullable(settings: ConverterSettings) = when(this) { Nullability.Default -> !settings.forceNotNullTypes } +enum class Mutability { + Mutable + NonMutable + Default +} + +fun Mutability.isMutable(settings: ConverterSettings) = when(this) { + Mutability.Mutable -> true + Mutability.NonMutable -> false + Mutability.Default -> false //TODO: setting? +} + abstract class MayBeNullableType(nullability: Nullability, val settings: ConverterSettings) : Type() { override val isNullable: Boolean = nullability.isNullable(settings)