Java to Kotlin converter: preparing to support Mutability
This commit is contained in:
committed by
valentin
parent
3217981a62
commit
1af63cb3be
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user