Java to Kotlin converter: preparing to support Mutability

This commit is contained in:
Valentin Kipyatkov
2014-10-23 18:01:00 +04:00
committed by valentin
parent 3217981a62
commit 1af63cb3be
2 changed files with 262 additions and 207 deletions
+250 -207
View File
@@ -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<PsiElement, Nullability>()
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<T>(val default: T) {
private val cache = HashMap<PsiElement, T>()
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>(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 {
@@ -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)