New J2K: add element info storage, pass info about types & functions via it

This commit is contained in:
Ilya Kirillov
2019-04-18 14:01:36 +03:00
committed by Ilya Kirillov
parent 39b09b39ff
commit 98651f3484
8 changed files with 255 additions and 70 deletions
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.nj2k.nullabilityAnalysis
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
@@ -12,26 +13,46 @@ 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.nj2k.*
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.callUtil.getType
import org.jetbrains.kotlin.resolve.calls.components.isVararg
import org.jetbrains.kotlin.resolve.descriptorUtil.firstOverridden
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
internal class LabelToFunctionTypeVariableMapper(analysisContext: AnalysisContext) {
private val labelToFunction = analysisContext.declarationToTypeVariable.mapNotNull { (declaration, _) ->
if (declaration !is KtNamedFunction) return@mapNotNull null
val label = declaration.nameIdentifier?.getLabel() ?: return@mapNotNull null
label to declaration
}.toMap()
fun functionByLabel(label: JKElementInfoLabel): KtNamedFunction? =
labelToFunction[label]
}
internal class ConstraintsCollector(
private val analysisContext: AnalysisContext,
private val converterContext: NewJ2kConverterContext,
printConstraints: Boolean
) {
private val boundTypeStorage = BoundTypeStorage(analysisContext, printConstraints)
private val constraintBuilder = ConstraintBuilder(boundTypeStorage)
private val labelToFunctionTypeVariableMapper = LabelToFunctionTypeVariableMapper(analysisContext)
private val PsiElement.elementInfo: List<JKElementInfo>?
get() = getLabel()?.let { label ->
converterContext.elementsInfoStorage.getInfoForLabel(label)
}
private inline val KtExpression.boundType
get() = boundTypeStorage.boundTypeFor(this)
@@ -161,47 +182,71 @@ internal class ConstraintsCollector(
}
private fun collectSuperDeclarationConstraintsForFunction(function: KtNamedFunction) = with(constraintBuilder) {
val descriptor = function.resolveToDescriptorIfAny() ?: return
val superDeclarationDescriptor = descriptor.firstOverridden { true }
?.takeIf { it != descriptor }
?.safeAs<FunctionDescriptor>()
?: return
val containingSuperClassBoundType =
superDeclarationDescriptor.containingDeclaration.safeAs<ClassDescriptor>()?.let { klass ->
GenericBoundType(
DescriptorClassReference(klass),
emptyList(),//TODO,
isNull = false
)
private fun SuperFunctionInfo.superFunctionBoundType(
originalFunctionDescriptor: FunctionDescriptor
): BoundType? =
when (this) {
is ExternalSuperFunctionInfo -> {
val containingSuperClassBoundType =
descriptor.containingDeclaration.safeAs<ClassDescriptor>()?.let { klass ->
GenericBoundType(
DescriptorClassReference(klass),
emptyList(),//TODO,
isNull = false
)
}
originalFunctionDescriptor.returnType?.let { type ->
boundTypeStorage.boundTypeForType(type, containingSuperClassBoundType, emptyMap()/*TODO*/)
}
}
is InternalSuperFunctionInfo -> {
labelToFunctionTypeVariableMapper.functionByLabel(label)?.let { function ->
analysisContext.declarationToTypeVariable[function]?.let { TypeVariableBoundType(it) }
}
}
}
val superFunctionPsi = superDeclarationDescriptor.findPsi() as? KtNamedFunction
private fun SuperFunctionInfo.superFunctionParameterBoundType(parameterIndex: Int): BoundType? {
return when (this) {
is ExternalSuperFunctionInfo -> {
val containingSuperClassBoundType =
descriptor.containingDeclaration.safeAs<ClassDescriptor>()?.let { klass ->
GenericBoundType(
DescriptorClassReference(klass),
emptyList(),//TODO,
isNull = false
)
}
val parameter = descriptor.valueParameters.getOrNull(parameterIndex) ?: return null
boundTypeStorage.boundTypeForType(parameter.type, containingSuperClassBoundType, emptyMap()/*TODO*/)
}
is InternalSuperFunctionInfo -> {
val function = labelToFunctionTypeVariableMapper.functionByLabel(label) ?: return null
val parameter = function.valueParameters.getOrNull(parameterIndex) ?: return null
analysisContext.declarationToTypeVariable[parameter]?.let { TypeVariableBoundType(it) }
}
}
}
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
)
private fun collectSuperDeclarationConstraintsForFunction(function: KtNamedFunction) = with(constraintBuilder) {
val (descriptor, superDeclarationDescriptors) =
function.nameIdentifier?.elementInfo?.firstIsInstanceOrNull<FunctionInfo>() ?: return@with
for (parameterIndex in function.valueParameters.indices) {
val parameterTypeVariable =
function.valueParameters[parameterIndex].let { analysisContext.declarationToTypeVariable[it] } ?: return
for (superDeclarationInfo in superDeclarationDescriptors) {
val superFunctionBoundType = superDeclarationInfo.superFunctionBoundType(descriptor) ?: continue
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)
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 = superDeclarationInfo.superFunctionParameterBoundType(parameterIndex) ?: return
parameterTypeVariable.addEqualsNullabilityConstraint(superParameterBoundType, ConstraintCameFrom.SUPER_DECLARATION)
}
}
}
@@ -9,12 +9,18 @@ import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.nj2k.NewJ2kConverterContext
import org.jetbrains.kotlin.nj2k.JKElementInfoLabel
import org.jetbrains.kotlin.nj2k.asLabel
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.elementsInRange
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
internal class ContextCreator(private val getNullability: (KtTypeElement) -> Nullability) {
internal class ContextCreator(
private val conversionContext: NewJ2kConverterContext,
private val getNullability: (KtTypeElement, NewJ2kConverterContext) -> Nullability
) {
private fun KtCallableDeclaration.typeElement(): KtTypeElement? =
typeReference?.typeElement
@@ -62,7 +68,7 @@ internal class ContextCreator(private val getNullability: (KtTypeElement) -> Nul
}
private fun KtTypeElement.asTypeVariable(): TypeVariable {
val nullability = getNullability(this)
val nullability = getNullability(this, conversionContext)
val classReference: ClassReference = classReference()
val typeParameters: List<TypeVariableTypeParameter> =
typeArgumentsAsTypes.mapIndexed { index, typeRef ->
@@ -79,38 +85,40 @@ internal class ContextCreator(private val getNullability: (KtTypeElement) -> Nul
}
fun nullabilityByUndefinedNullabilityComment(typeElement: KtTypeElement): Nullability {
val undefinedNullabilityComment =
typeElement.parent?.safeAs<KtTypeReference>()?.getUndefinedNullabilityComment()?.also { it.delete() }
fun nullabilityByUndefinedNullabilityComment(typeElement: KtTypeElement, context: NewJ2kConverterContext): Nullability {
val hasUndefinedNullabilityLabel =
typeElement.parent?.safeAs<KtTypeReference>()?.hasUndefinedNullabilityLabel(context) ?: false
return when {
undefinedNullabilityComment != null -> Nullability.UNKNOWN
hasUndefinedNullabilityLabel -> Nullability.UNKNOWN
typeElement is KtNullableType -> Nullability.NULLABLE
else -> Nullability.NOT_NULL
}
}
private fun KtElement.getUndefinedNullabilityComment(): PsiComment? =
prevSibling?.safeAs<PsiComment>()?.takeIf { it.text == UNDEFINED_NULLABILITY_COMMENT }
?: parent?.safeAs<KtTypeProjection>()?.getUndefinedNullabilityComment()
fun PsiElement.getLabel(): JKElementInfoLabel? =
prevSibling?.safeAs<PsiComment>()?.text?.asLabel()
?: parent?.safeAs<KtTypeProjection>()?.getLabel()
private fun KtTypeReference.hasUndefinedNullabilityLabel(context: NewJ2kConverterContext) =
getLabel()?.let { label ->
context.elementsInfoStorage.getInfoForLabel(label).orEmpty().contains(org.jetbrains.kotlin.nj2k.UnknownNullability)
} ?: false
fun prepareTypeElementByMakingAllTypesNullableConsideringNullabilityComment(typeElement: KtTypeElement) {
val hasUndefinedNullabilityComment =
typeElement.parent?.safeAs<KtTypeReference>()?.getUndefinedNullabilityComment() != null
if (hasUndefinedNullabilityComment) {
fun prepareTypeElementByMakingAllTypesNullableConsideringNullabilityComment(typeElement: KtTypeElement, context: NewJ2kConverterContext) {
val hasUndefinedNullabilityLabel =
typeElement.parent?.safeAs<KtTypeReference>()?.hasUndefinedNullabilityLabel(context) ?: false
if (hasUndefinedNullabilityLabel) {
typeElement.changeNullability(toNullable = true)
}
}
fun preapareTypeElementByMakingAllTypesNullable(typeElement: KtTypeElement) {
fun prepareTypeElementByMakingAllTypesNullable(typeElement: KtTypeElement, conversionContext: NewJ2kConverterContext) {
typeElement.changeNullability(toNullable = true)
}
internal const val UNDEFINED_NULLABILITY_COMMENT = "/*UNDEFINED*/"
internal data class AnalysisContext(
val typeVariableOwners: List<TypeVariableOwner>,
val typeElementToTypeVariable: Map<KtTypeElement, TypeVariable>,
@@ -12,6 +12,8 @@ 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.NewJ2kConverterContext
import org.jetbrains.kotlin.nj2k.asLabel
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
@@ -73,22 +75,23 @@ internal fun KtTypeElement.classReference(): ClassReference {
}
class NullabilityAnalysisFacade(
private val getTypeElementNullability: (KtTypeElement) -> Nullability,
private val prepareTypeElement: (KtTypeElement) -> Unit,
private val conversionContext: NewJ2kConverterContext,
private val getTypeElementNullability: (KtTypeElement, NewJ2kConverterContext) -> Nullability,
private val prepareTypeElement: (KtTypeElement, NewJ2kConverterContext) -> Unit,
private val debugPrint: Boolean
) {
fun fixNullability(analysisScope: AnalysisScope) {
CommandProcessor.getInstance().runUndoTransparentAction {
runWriteAction {
analysisScope.prepareTypeElements(prepareTypeElement)
val context = ContextCreator(getTypeElementNullability).createContext(analysisScope)
analysisScope.prepareTypeElements(prepareTypeElement, conversionContext)
val context = ContextCreator(conversionContext, getTypeElementNullability).createContext(analysisScope)
if (debugPrint) {
with(Printer(context)) {
analysisScope.forEach { it.addTypeVariablesNames() }
}
}
val constraints = ConstraintsCollector(context, debugPrint).collectConstraints(analysisScope)
val constraints = ConstraintsCollector(context, conversionContext, debugPrint).collectConstraints(analysisScope)
Solver(context, debugPrint).solveConstraints(constraints)
context.fixTypeVariablesNullability()
analysisScope.clearUndefinedLabels()
@@ -97,12 +100,15 @@ class NullabilityAnalysisFacade(
}
}
private fun AnalysisScope.prepareTypeElements(prepareTypeElement: (KtTypeElement) -> Unit) {
private fun AnalysisScope.prepareTypeElements(
prepareTypeElement: (KtTypeElement, NewJ2kConverterContext) -> Unit,
conversionContext: NewJ2kConverterContext
) {
val typeElements = flatMap { it.collectDescendantsOfType<KtTypeReference>() }
typeElements.forEach { typeReference ->
val typeElement = typeReference.typeElement ?: return@forEach
if (typeElement.parentOfType<KtSuperTypeCallEntry>() == null) {
prepareTypeElement(typeElement)
prepareTypeElement(typeElement, conversionContext)
}
}
}
@@ -117,7 +123,7 @@ private fun AnalysisScope.clearUndefinedLabels() {
}
override fun visitComment(comment: PsiComment) {
if (comment.text == UNDEFINED_NULLABILITY_COMMENT) {
if (comment.text.asLabel() != null) {
comments += comment
}
}
@@ -0,0 +1,63 @@
/*
* 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
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
interface JKElementInfo
sealed class SuperFunctionInfo
data class ExternalSuperFunctionInfo(val descriptor: FunctionDescriptor) : SuperFunctionInfo()
data class InternalSuperFunctionInfo(val label: JKElementInfoLabel) : SuperFunctionInfo()
data class FunctionInfo(val originalDescriptor: FunctionDescriptor, val superFunctions: List<SuperFunctionInfo>) : JKElementInfo
object UnknownNullability : JKElementInfo
inline class JKElementInfoLabel(val label: String) {
inline fun render(): String = "/*@@$label@@*/"
companion object {
val LABEL_REGEX = """/\*@@(\w+)@@\*/""".toRegex()
}
}
fun String.asLabel(): JKElementInfoLabel? =
JKElementInfoLabel.LABEL_REGEX.matchEntire(this)?.groupValues?.getOrNull(1)?.let { JKElementInfoLabel(it) }
class JKElementInfoStorage {
private val labelToInfo = mutableMapOf<JKElementInfoLabel, List<JKElementInfo>>()
private val elementToLabel = mutableMapOf<Any, JKElementInfoLabel>()
fun getOrCreateInfoForElement(element: Any): JKElementInfoLabel =
elementToLabel.getOrPut(element) { JKElementInfoLabel(createRandomString()) }
fun getOrCreateInfoForLabel(label: JKElementInfoLabel): List<JKElementInfo> =
labelToInfo.getOrPut(label) { emptyList() }
fun getInfoForLabel(label: JKElementInfoLabel): List<JKElementInfo>? =
labelToInfo[label]
fun addEntry(element: Any, info: JKElementInfo) {
val label = elementToLabel.getOrPut(element) { JKElementInfoLabel(createRandomString()) }
labelToInfo[label] = labelToInfo[label].orEmpty() + info
elementToLabel[element] = label
}
companion object {
private val charPool = ('a'..'z').toList()
private const val generatedStringLength = 6
private fun createRandomString(): String {
return (1..generatedStringLength)
.map { kotlin.random.Random.nextInt(0, charPool.size) }
.map(charPool::get)
.joinToString("")
}
}
}
@@ -28,8 +28,8 @@ import org.jetbrains.kotlin.nj2k.tree.visitors.JKVisitorWithCommentsPrinting
import org.jetbrains.kotlin.utils.Printer
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class NewCodeBuilder {
class NewCodeBuilder(context: NewJ2kConverterContext) {
private val elementInfoStorage = context.elementsInfoStorage
val builder = StringBuilder()
val printer = Printer(builder)
@@ -399,6 +399,10 @@ class NewCodeBuilder {
renderModifiersList(ktFunction)
printer.printWithNoIndent(" fun ")
ktFunction.typeParameterList.accept(this)
elementInfoStorage.getOrCreateInfoForElement(ktFunction)?.also {
printer.printWithNoIndent(it.render())
}
ktFunction.name.accept(this)
renderTokenElement(ktFunction.leftParen)
renderList(ktFunction.parameters) {
@@ -652,8 +656,8 @@ class NewCodeBuilder {
private fun renderType(type: JKType, owner: JKTreeElement?) {
if (type is JKNoTypeImpl) return
if (type.nullability == Nullability.Default) {
printer.print("/*UNDEFINED*/")
elementInfoStorage.getOrCreateInfoForElement(type)?.also {
printer.print(it.render())
}
when (type) {
is JKClassType -> {
@@ -15,7 +15,7 @@ data class NewJ2kConverterContext(
val converter: NewJavaToKotlinConverter,
val inConversionContext: (PsiElement) -> Boolean,
val importStorage: ImportStorage,
val elementsInfoStorage: ElementInfoStorage
val elementsInfoStorage: JKElementInfoStorage
) : ConverterContext {
val project: Project get() = converter.project
val typeFlavorCalculator = TypeFlavorCalculator(object : TypeFlavorConverterFacade {
@@ -40,7 +40,7 @@ data class NewJ2kConverterContext(
),
{ false },
ImportStorage(),
ElementInfoStorage()
JKElementInfoStorage()
)
}
@@ -101,13 +101,14 @@ class NewJavaToKotlinConverter(
symbolProvider,
this,
{ it.containingFile in inputElements },
importStorage
importStorage,
JKElementInfoStorage()
)
ConversionsRunner.doApply(asts.mapNotNull { it.second }, context)
val results = asts.map { (element, ast) ->
if (ast == null) return@map null
val code = NewCodeBuilder().run { printCodeOut(ast) }
val code = NewCodeBuilder(context).run { printCodeOut(ast) }
val parseContext = when (element) {
is PsiStatement, is PsiExpression -> ParseContext.CODE_BLOCK
else -> ParseContext.TOP_LEVEL
@@ -0,0 +1,58 @@
/*
* 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.conversions
import com.intellij.psi.PsiMethod
import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaMethodDescriptor
import org.jetbrains.kotlin.j2k.ast.Nullability
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
import org.jetbrains.kotlin.nj2k.*
import org.jetbrains.kotlin.nj2k.tree.*
import org.jetbrains.kotlin.nj2k.tree.impl.JKUniverseMethodSymbol
import org.jetbrains.kotlin.nj2k.tree.impl.psi
class AddElementsInfoConversion(private val context: NewJ2kConverterContext) : RecursiveApplicableConversionBase() {
override fun applyToElement(element: JKTreeElement): JKTreeElement {
when (element) {
is JKTypeElement -> addInfoForTypeElement(element)
is JKKtFunction -> addInfoForFunction(element)
}
return recurse(element)
}
private fun addInfoForTypeElement(typeElement: JKTypeElement) {
typeElement.type.forAllInnerTypes { type ->
if (type.nullability == Nullability.Default) {
context.elementsInfoStorage.addEntry(type, UnknownNullability)
}
}
}
private fun addInfoForFunction(function: JKKtFunction) {
val psiMethod = function.psi<PsiMethod>() ?: return
val descriptor = psiMethod.getJavaMethodDescriptor() ?: return
val superDescriptorsInfo =
descriptor.overriddenDescriptors.map { superDescriptor ->
val psi = superDescriptor.findPsi() ?: return@map ExternalSuperFunctionInfo(descriptor)
when (val symbol = context.symbolProvider.symbolsByPsi[psi]) {
is JKUniverseMethodSymbol ->
InternalSuperFunctionInfo(
context.elementsInfoStorage.getOrCreateInfoForElement(symbol.target)
)
else -> ExternalSuperFunctionInfo(descriptor)
}
}
context.elementsInfoStorage.addEntry(function, FunctionInfo(descriptor, superDescriptorsInfo))
}
private fun JKType.forAllInnerTypes(action: (JKType) -> Unit) {
action(this)
if (this is JKParametrizedType) {
parameters.forEach { it.forAllInnerTypes(action) }
}
}
}