New J2K: implement mutability inference in post-processing
#KT-24293 fixed #KT-19603 fixed
This commit is contained in:
@@ -143,6 +143,7 @@ import org.jetbrains.kotlin.nj2k.AbstractNewJavaToKotlinConverterSingleFileTest
|
||||
import org.jetbrains.kotlin.nj2k.AbstractNewJavaToKotlinCopyPasteConversionTest
|
||||
import org.jetbrains.kotlin.nj2k.AbstractTextNewJavaToKotlinCopyPasteConversionTest
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.AbstractCommonConstraintCollectorTest
|
||||
import org.jetbrains.kotlin.nj2k.inference.mutability.AbstractMutabilityInferenceTest
|
||||
import org.jetbrains.kotlin.nj2k.inference.nullability.AbstractNullabilityInferenceTest
|
||||
import org.jetbrains.kotlin.noarg.AbstractBlackBoxCodegenTestForNoArg
|
||||
import org.jetbrains.kotlin.noarg.AbstractBytecodeListingTestForNoArg
|
||||
@@ -1062,6 +1063,9 @@ fun main(args: Array<String>) {
|
||||
testClass<AbstractNullabilityInferenceTest> {
|
||||
model("inference/nullability")
|
||||
}
|
||||
testClass<AbstractMutabilityInferenceTest> {
|
||||
model("inference/mutability")
|
||||
}
|
||||
testClass<AbstractNewJavaToKotlinCopyPasteConversionTest> {
|
||||
model("copyPaste", pattern = """^([^\.]+)\.java$""")
|
||||
}
|
||||
|
||||
@@ -161,6 +161,7 @@ import org.jetbrains.kotlin.nj2k.AbstractNewJavaToKotlinConverterSingleFileTest
|
||||
import org.jetbrains.kotlin.nj2k.AbstractNewJavaToKotlinCopyPasteConversionTest
|
||||
import org.jetbrains.kotlin.nj2k.AbstractTextNewJavaToKotlinCopyPasteConversionTest
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.AbstractCommonConstraintCollectorTest
|
||||
import org.jetbrains.kotlin.nj2k.inference.mutability.AbstractMutabilityInferenceTest
|
||||
import org.jetbrains.kotlin.nj2k.inference.nullability.AbstractNullabilityInferenceTest
|
||||
import org.jetbrains.kotlin.noarg.AbstractBlackBoxCodegenTestForNoArg
|
||||
import org.jetbrains.kotlin.noarg.AbstractBytecodeListingTestForNoArg
|
||||
@@ -1005,6 +1006,9 @@ fun main(args: Array<String>) {
|
||||
testClass<AbstractNullabilityInferenceTest> {
|
||||
model("inference/nullability")
|
||||
}
|
||||
testClass<AbstractMutabilityInferenceTest> {
|
||||
model("inference/mutability")
|
||||
}
|
||||
testClass<AbstractNewJavaToKotlinCopyPasteConversionTest> {
|
||||
model("copyPaste", pattern = """^([^\.]+)\.java$""")
|
||||
}
|
||||
|
||||
@@ -145,6 +145,7 @@ import org.jetbrains.kotlin.nj2k.AbstractNewJavaToKotlinConverterSingleFileTest
|
||||
import org.jetbrains.kotlin.nj2k.AbstractNewJavaToKotlinCopyPasteConversionTest
|
||||
import org.jetbrains.kotlin.nj2k.AbstractTextNewJavaToKotlinCopyPasteConversionTest
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.AbstractCommonConstraintCollectorTest
|
||||
import org.jetbrains.kotlin.nj2k.inference.mutability.AbstractMutabilityInferenceTest
|
||||
import org.jetbrains.kotlin.nj2k.inference.nullability.AbstractNullabilityInferenceTest
|
||||
import org.jetbrains.kotlin.noarg.AbstractBlackBoxCodegenTestForNoArg
|
||||
import org.jetbrains.kotlin.noarg.AbstractBytecodeListingTestForNoArg
|
||||
@@ -969,6 +970,9 @@ fun main(args: Array<String>) {
|
||||
testClass<AbstractNullabilityInferenceTest> {
|
||||
model("inference/nullability")
|
||||
}
|
||||
testClass<AbstractMutabilityInferenceTest> {
|
||||
model("inference/mutability")
|
||||
}
|
||||
testClass<AbstractNewJavaToKotlinCopyPasteConversionTest> {
|
||||
model("copyPaste", pattern = """^([^\.]+)\.java$""")
|
||||
}
|
||||
|
||||
@@ -145,6 +145,7 @@ import org.jetbrains.kotlin.nj2k.AbstractNewJavaToKotlinConverterSingleFileTest
|
||||
import org.jetbrains.kotlin.nj2k.AbstractNewJavaToKotlinCopyPasteConversionTest
|
||||
import org.jetbrains.kotlin.nj2k.AbstractTextNewJavaToKotlinCopyPasteConversionTest
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.AbstractCommonConstraintCollectorTest
|
||||
import org.jetbrains.kotlin.nj2k.inference.mutability.AbstractMutabilityInferenceTest
|
||||
import org.jetbrains.kotlin.nj2k.inference.nullability.AbstractNullabilityInferenceTest
|
||||
import org.jetbrains.kotlin.noarg.AbstractBlackBoxCodegenTestForNoArg
|
||||
import org.jetbrains.kotlin.noarg.AbstractBytecodeListingTestForNoArg
|
||||
@@ -965,6 +966,9 @@ fun main(args: Array<String>) {
|
||||
testClass<AbstractNullabilityInferenceTest> {
|
||||
model("inference/nullability")
|
||||
}
|
||||
testClass<AbstractMutabilityInferenceTest> {
|
||||
model("inference/mutability")
|
||||
}
|
||||
testClass<AbstractNewJavaToKotlinCopyPasteConversionTest> {
|
||||
model("copyPaste", pattern = """^([^\.]+)\.java$""")
|
||||
}
|
||||
|
||||
@@ -145,6 +145,7 @@ import org.jetbrains.kotlin.nj2k.AbstractNewJavaToKotlinConverterSingleFileTest
|
||||
import org.jetbrains.kotlin.nj2k.AbstractNewJavaToKotlinCopyPasteConversionTest
|
||||
import org.jetbrains.kotlin.nj2k.AbstractTextNewJavaToKotlinCopyPasteConversionTest
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.AbstractCommonConstraintCollectorTest
|
||||
import org.jetbrains.kotlin.nj2k.inference.mutability.AbstractMutabilityInferenceTest
|
||||
import org.jetbrains.kotlin.nj2k.inference.nullability.AbstractNullabilityInferenceTest
|
||||
import org.jetbrains.kotlin.noarg.AbstractBlackBoxCodegenTestForNoArg
|
||||
import org.jetbrains.kotlin.noarg.AbstractBytecodeListingTestForNoArg
|
||||
@@ -965,6 +966,9 @@ fun main(args: Array<String>) {
|
||||
testClass<AbstractNullabilityInferenceTest> {
|
||||
model("inference/nullability")
|
||||
}
|
||||
testClass<AbstractMutabilityInferenceTest> {
|
||||
model("inference/mutability")
|
||||
}
|
||||
testClass<AbstractNewJavaToKotlinCopyPasteConversionTest> {
|
||||
model("copyPaste", pattern = """^([^\.]+)\.java$""")
|
||||
}
|
||||
|
||||
+8
-5
@@ -88,9 +88,7 @@ open class BoundTypeCalculatorImpl(
|
||||
valueParameters.map { parameter ->
|
||||
parameter.typeReference?.typeElement?.let { typeElement ->
|
||||
inferenceContext.typeElementToTypeVariable[typeElement]
|
||||
}?.let { typeVariable ->
|
||||
typeVariable.asBoundType()
|
||||
} ?: return null
|
||||
}?.asBoundType() ?: return null
|
||||
}
|
||||
} else {
|
||||
descriptor.valueParameters.map { parameter ->
|
||||
@@ -220,7 +218,12 @@ open class BoundTypeCalculatorImpl(
|
||||
)
|
||||
|
||||
is TypeParameterDescriptor -> {
|
||||
val containingDeclaration = target.containingDeclaration
|
||||
val containingDeclaration = target.containingDeclaration.let { container ->
|
||||
when (container) {
|
||||
is TypeAliasDescriptor -> container.classDescriptor
|
||||
else -> container
|
||||
} ?: container
|
||||
}
|
||||
when {
|
||||
containingDeclaration == call?.candidateDescriptor?.original -> {
|
||||
val returnTypeVariable = inferenceContext.typeElementToTypeVariable[
|
||||
@@ -240,7 +243,7 @@ open class BoundTypeCalculatorImpl(
|
||||
contextBoundType?.isReferenceToClass == true ->
|
||||
contextBoundType.typeParameters.getOrNull(target.index)?.boundType
|
||||
|
||||
// `this` or `super` call case
|
||||
// `this`, `super`, or constructor call cases
|
||||
containingDeclaration == call?.candidateDescriptor.safeAs<ConstructorDescriptor>()?.constructedClass -> {
|
||||
val returnTypeVariable = inferenceContext.typeElementToTypeVariable[
|
||||
call?.call?.typeArguments?.getOrNull(target.index)?.typeReference?.typeElement
|
||||
|
||||
@@ -54,8 +54,15 @@ class LiteralBound private constructor(val state: State) : ConstraintBound() {
|
||||
}
|
||||
}
|
||||
|
||||
fun State.constraintBound(): LiteralBound = when (this) {
|
||||
fun State.constraintBound(): LiteralBound? = when (this) {
|
||||
State.LOWER -> LiteralBound.LOWER
|
||||
State.UPPER -> LiteralBound.UPPER
|
||||
State.UNKNOWN -> LiteralBound.UNKNOWN
|
||||
State.UNUSED -> null
|
||||
}
|
||||
|
||||
val ConstraintBound.isUnused
|
||||
get() = when (this) {
|
||||
is TypeVariableBound -> typeVariable.state == State.UNUSED
|
||||
is LiteralBound -> state == State.UNUSED
|
||||
}
|
||||
|
||||
+5
-1
@@ -40,7 +40,11 @@ class ConstraintBuilder(
|
||||
|
||||
fun KtExpression.isTheSameTypeAs(other: State, priority: ConstraintPriority) {
|
||||
boundType().label.safeAs<TypeVariableLabel>()?.typeVariable?.let { typeVariable ->
|
||||
constraints += EqualsConstraint(typeVariable.constraintBound(), other.constraintBound(), priority)
|
||||
constraints += EqualsConstraint(
|
||||
typeVariable.constraintBound(),
|
||||
other.constraintBound() ?: return@let,
|
||||
priority
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+22
-40
@@ -16,7 +16,6 @@ import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.typeUtil.isUnit
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
abstract class ContextCollector(private val resolutionFacade: ResolutionFacade) {
|
||||
@@ -48,11 +47,7 @@ abstract class ContextCollector(private val resolutionFacade: ResolutionFacade)
|
||||
val typeElement = typeElement ?: return null
|
||||
val classReference = classReference() ?: NoClassReference
|
||||
|
||||
val state = when {
|
||||
classReference.descriptor?.defaultType?.isUnit() == true -> State.LOWER
|
||||
defaultState != null -> defaultState
|
||||
else -> classReference.getState(typeElement)
|
||||
}
|
||||
val state = defaultState ?: classReference.getState(typeElement)
|
||||
val typeArguments =
|
||||
if (classReference is DescriptorClassReference) {
|
||||
typeElement.typeArgumentsAsTypes.zip(
|
||||
@@ -65,22 +60,15 @@ abstract class ContextCollector(private val resolutionFacade: ResolutionFacade)
|
||||
}
|
||||
} else emptyList()
|
||||
|
||||
return if (state == null) {
|
||||
BoundTypeImpl(
|
||||
GenericLabel(classReference),
|
||||
typeArguments
|
||||
)
|
||||
} else {
|
||||
val typeVariable = TypeElementBasedTypeVariable(
|
||||
classReference,
|
||||
typeArguments,
|
||||
typeElement.toData() ?: return null,
|
||||
owner,
|
||||
state
|
||||
)
|
||||
typeElementToTypeVariable[typeElement] = typeVariable
|
||||
typeVariable.asBoundType()
|
||||
}
|
||||
val typeVariable = TypeElementBasedTypeVariable(
|
||||
classReference,
|
||||
typeArguments,
|
||||
typeElement.toData() ?: return null,
|
||||
owner,
|
||||
state
|
||||
)
|
||||
typeElementToTypeVariable[typeElement] = typeVariable
|
||||
return typeVariable.asBoundType()
|
||||
}
|
||||
|
||||
fun KotlinType.toBoundType(): BoundType? {
|
||||
@@ -97,24 +85,18 @@ abstract class ContextCollector(private val resolutionFacade: ResolutionFacade)
|
||||
}
|
||||
} else emptyList()
|
||||
|
||||
return if (state == null) {
|
||||
BoundTypeImpl(
|
||||
GenericLabel(classReference),
|
||||
typeArguments
|
||||
)
|
||||
} else {
|
||||
val typeVariable = TypeBasedTypeVariable(
|
||||
classReference,
|
||||
typeArguments,
|
||||
this,
|
||||
state
|
||||
)
|
||||
typeBasedTypeVariables += typeVariable
|
||||
typeVariable.asBoundType()
|
||||
}
|
||||
|
||||
val typeVariable = TypeBasedTypeVariable(
|
||||
classReference,
|
||||
typeArguments,
|
||||
this,
|
||||
state
|
||||
)
|
||||
typeBasedTypeVariables += typeVariable
|
||||
return typeVariable.asBoundType()
|
||||
}
|
||||
|
||||
val substitutors = mutableMapOf<ClassDescriptor, ClassSubstitutor>()
|
||||
val substitutors = mutableMapOf<ClassDescriptor, SuperTypesSubstitutor>()
|
||||
|
||||
fun isOrAsExpression(typeReference: KtTypeReference) {
|
||||
val typeElement = typeReference.typeElement ?: return
|
||||
@@ -160,7 +142,7 @@ abstract class ContextCollector(private val resolutionFacade: ResolutionFacade)
|
||||
val descriptor =
|
||||
expression.resolveToDescriptorIfAny(resolutionFacade) ?: return@forEachDescendantOfType
|
||||
substitutors[descriptor] =
|
||||
ClassSubstitutor.createFromKtClass(expression, resolutionFacade) ?: return@forEachDescendantOfType
|
||||
SuperTypesSubstitutor.createFromKtClass(expression, resolutionFacade) ?: return@forEachDescendantOfType
|
||||
for (typeParameter in expression.typeParameters) {
|
||||
val typeVariable = typeParameter.resolveToDescriptorIfAny(resolutionFacade)
|
||||
?.safeAs<TypeParameterDescriptor>()
|
||||
@@ -206,5 +188,5 @@ abstract class ContextCollector(private val resolutionFacade: ResolutionFacade)
|
||||
)
|
||||
}
|
||||
|
||||
abstract fun ClassReference.getState(typeElement: KtTypeElement?): State?
|
||||
abstract fun ClassReference.getState(typeElement: KtTypeElement?): State
|
||||
}
|
||||
@@ -35,6 +35,7 @@ class DebugPrinter(private val inferenceContext: InferenceContext) {
|
||||
State.LOWER -> "L"
|
||||
State.UPPER -> "U"
|
||||
State.UNKNOWN -> "?"
|
||||
State.UNUSED -> "$"
|
||||
}
|
||||
|
||||
fun BoundType.asString(): String = buildString {
|
||||
@@ -71,11 +72,10 @@ class DebugPrinter(private val inferenceContext: InferenceContext) {
|
||||
|
||||
|
||||
private class Namer(inferenceContext: InferenceContext) {
|
||||
val names = inferenceContext.typeVariables.mapIndexed { index, typeVariable ->
|
||||
private val names = inferenceContext.typeVariables.mapIndexed { index, typeVariable ->
|
||||
typeVariable to "T$index"
|
||||
}.toMap()
|
||||
|
||||
|
||||
fun name(typeVariable: TypeVariable): String =
|
||||
names.getValue(typeVariable)
|
||||
}
|
||||
+1
-1
@@ -19,7 +19,7 @@ data class InferenceContext(
|
||||
val typeElementToTypeVariable: Map<KtTypeElement, TypeVariable>,
|
||||
val declarationToTypeVariable: Map<KtNamedDeclaration, TypeVariable>,
|
||||
val declarationDescriptorToTypeVariable: Map<DeclarationDescriptor, TypeVariable>,
|
||||
val classSubstitutions: Map<ClassDescriptor, ClassSubstitutor>
|
||||
val superTypesSubstitutions: Map<ClassDescriptor, SuperTypesSubstitutor>
|
||||
|
||||
) {
|
||||
fun isInConversionScope(childCandidate: PsiElement) = when (childCandidate) {
|
||||
|
||||
@@ -25,7 +25,9 @@ internal class Solver(
|
||||
println()
|
||||
println("type variables:")
|
||||
for (typeVariable in inferenceContext.typeVariables) {
|
||||
println("${typeVariable.name} := ${typeVariable.state}")
|
||||
if (typeVariable.state != State.UNUSED) {
|
||||
println("${typeVariable.name} := ${typeVariable.state}")
|
||||
}
|
||||
}
|
||||
println("---------------\n")
|
||||
}
|
||||
@@ -33,6 +35,7 @@ internal class Solver(
|
||||
|
||||
fun solveConstraints(constraints: List<Constraint>) {
|
||||
val mutableConstraints = constraints.toMutableList()
|
||||
mutableConstraints.filterOutConstraintsWithUnusedState()
|
||||
var currentStep = ConstraintPriority.values().first()
|
||||
|
||||
var i = 0
|
||||
@@ -144,11 +147,11 @@ internal class Solver(
|
||||
val (lower, upper) = constraint
|
||||
if (lower is TypeVariableBound && lower.typeVariable.isFixed) {
|
||||
somethingChanged = true
|
||||
constraint.subtype = lower.typeVariable.state.constraintBound()
|
||||
constraint.subtype = lower.typeVariable.state.constraintBound() ?: continue
|
||||
}
|
||||
if (upper is TypeVariableBound && upper.typeVariable.isFixed) {
|
||||
somethingChanged = true
|
||||
constraint.supertype = upper.typeVariable.state.constraintBound()
|
||||
constraint.supertype = upper.typeVariable.state.constraintBound() ?: continue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,5 +193,14 @@ internal class Solver(
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun MutableList<Constraint>.filterOutConstraintsWithUnusedState() {
|
||||
removeIf { constraint ->
|
||||
when (constraint) {
|
||||
is SubtypeConstraint -> constraint.subtype.isUnused || constraint.supertype.isUnused
|
||||
is EqualsConstraint -> constraint.left.isUnused || constraint.right.isUnused
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,14 @@
|
||||
package org.jetbrains.kotlin.nj2k.inference.common
|
||||
|
||||
enum class State {
|
||||
// lower state in the terms of subtyping relation,
|
||||
// e.g., for nullability this is not null type as T <: T?
|
||||
// for mutability this is MutableCollection as MutableCollection <: Collection
|
||||
LOWER,
|
||||
// the same as with lower but upper
|
||||
UPPER,
|
||||
UNKNOWN
|
||||
// the type variable state is needed to be calculated
|
||||
UNKNOWN,
|
||||
// we don't need to infer state of that type variable
|
||||
UNUSED
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.nj2k.inference.common
|
||||
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
|
||||
abstract class StateUpdater {
|
||||
@@ -14,12 +15,14 @@ abstract class StateUpdater {
|
||||
val deepComparator = Comparator<TypeElementBasedTypeVariable> { o1, o2 ->
|
||||
if (o1.typeElement.typeElement.isAncestor(o2.typeElement.typeElement)) 1 else -1
|
||||
}
|
||||
for (typeVariable in inferenceContext
|
||||
.typeVariables
|
||||
val typeVariablesSortedByDeep = inferenceContext.typeVariables
|
||||
.filterIsInstance<TypeElementBasedTypeVariable>()
|
||||
.sortedWith(deepComparator)
|
||||
) {
|
||||
typeVariable.updateState()
|
||||
|
||||
runWriteAction {
|
||||
for (typeVariable in typeVariablesSortedByDeep) {
|
||||
typeVariable.updateState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -14,12 +14,12 @@ import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtTypeElement
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
|
||||
class ClassSubstitutor(private val superTypes: Map<ClassifierDescriptor, Map<TypeParameterDescriptor, KtTypeElement>>) {
|
||||
class SuperTypesSubstitutor(private val superTypes: Map<ClassifierDescriptor, Map<TypeParameterDescriptor, KtTypeElement>>) {
|
||||
operator fun get(klass: ClassDescriptor, typeParameter: TypeParameterDescriptor): KtTypeElement? =
|
||||
superTypes[klass]?.get(typeParameter)
|
||||
|
||||
companion object {
|
||||
fun createFromKtClass(klass: KtClassOrObject, resolutionFacade: ResolutionFacade): ClassSubstitutor? {
|
||||
fun createFromKtClass(klass: KtClassOrObject, resolutionFacade: ResolutionFacade): SuperTypesSubstitutor? {
|
||||
val superTypes =
|
||||
klass.superTypeListEntries.map { superType ->
|
||||
val typeReference = superType.typeReference ?: return null
|
||||
@@ -31,7 +31,7 @@ class ClassSubstitutor(private val superTypes: Map<ClassifierDescriptor, Map<Typ
|
||||
parameter to (argument.typeElement ?: return null)
|
||||
}.toMap()
|
||||
}.toMap()
|
||||
return ClassSubstitutor(superTypes)
|
||||
return SuperTypesSubstitutor(superTypes)
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-6
@@ -10,10 +10,7 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.BoundTypeCalculator
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.ConstraintBuilder
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.ConstraintPriority
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.InferenceContext
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.*
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.asAssignment
|
||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.getTargetFunction
|
||||
@@ -108,8 +105,9 @@ class CommonConstraintsCollector : ConstraintsCollector() {
|
||||
contextBoundType = loopRangeBoundType,
|
||||
inferenceContext = inferenceContext
|
||||
) ?: return
|
||||
loopParameterTypeVariable.isSubtypeOf(
|
||||
boundType.typeParameters.firstOrNull()?.boundType ?: return,
|
||||
|
||||
boundType.typeParameters.firstOrNull()?.boundType?.isSubtypeOf(
|
||||
loopParameterTypeVariable.asBoundType(),
|
||||
ConstraintPriority.ASSIGNMENT
|
||||
)
|
||||
}
|
||||
@@ -131,5 +129,6 @@ class CommonConstraintsCollector : ConstraintsCollector() {
|
||||
?.firstOrNull { it.valueParameters.isEmpty() }
|
||||
?.original
|
||||
?.returnType
|
||||
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -34,7 +34,7 @@ class FunctionConstraintsCollector(
|
||||
val classDescriptor = ktClass.resolveToDescriptorIfAny(resolutionFacade) ?: return
|
||||
|
||||
val superFunctions = superFunctionsProvider.provideSuperFunctionDescriptors(element) ?: return
|
||||
val substitutor = inferenceContext.classSubstitutions[classDescriptor] ?: return
|
||||
val substitutor = inferenceContext.superTypesSubstitutions[classDescriptor] ?: return
|
||||
|
||||
for (superFunction in superFunctions) {
|
||||
val superClass = superFunction.containingDeclaration as? ClassDescriptor ?: continue
|
||||
@@ -68,7 +68,7 @@ class FunctionConstraintsCollector(
|
||||
typeElement: KtTypeElement,
|
||||
superType: KotlinType,
|
||||
superTypeElement: KtTypeElement?,
|
||||
substitutor: ClassSubstitutor,
|
||||
substitutor: SuperTypesSubstitutor,
|
||||
superClass: ClassDescriptor,
|
||||
inferenceContext: InferenceContext
|
||||
) {
|
||||
|
||||
@@ -7,14 +7,10 @@ package org.jetbrains.kotlin.nj2k.inference.common
|
||||
|
||||
import com.intellij.psi.PsiComment
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.idea.refactoring.isInterfaceClass
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.nj2k.*
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.jetbrains.kotlin.psi.KtTypeElement
|
||||
import org.jetbrains.kotlin.psi.KtTypeProjection
|
||||
import org.jetbrains.kotlin.psi.KtTypeReference
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
fun PsiElement.getLabel(): JKElementInfoLabel? =
|
||||
@@ -33,12 +29,12 @@ fun PsiElement.elementInfo(converterContext: NewJ2kConverterContext): List<JKEle
|
||||
}
|
||||
|
||||
|
||||
private fun KtTypeReference.hasUnknownLabel(context: NewJ2kConverterContext, unknownLabel: JKElementInfo) =
|
||||
inline fun KtTypeReference.hasUnknownLabel(context: NewJ2kConverterContext, isUnknownLabel: (JKTypeInfo) -> Boolean) =
|
||||
getLabel()?.let { label ->
|
||||
unknownLabel in context.elementsInfoStorage.getInfoForLabel(label).orEmpty()
|
||||
context.elementsInfoStorage.getInfoForLabel(label)?.any { it.safeAs<JKTypeInfo>()?.let(isUnknownLabel) == true }
|
||||
} ?: false
|
||||
|
||||
fun KtTypeElement.hasUnknownLabel(context: NewJ2kConverterContext, unknownLabel: JKElementInfo) =
|
||||
inline fun KtTypeElement.hasUnknownLabel(context: NewJ2kConverterContext, isUnknownLabel: (JKTypeInfo) -> Boolean) =
|
||||
parent
|
||||
?.safeAs<KtTypeReference>()
|
||||
?.hasUnknownLabel(context, unknownLabel) == true
|
||||
?.hasUnknownLabel(context, isUnknownLabel) == true
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.inference.mutability
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtReferenceExpression
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
|
||||
|
||||
class CallResolver(private val fqNames: Set<FqName>) {
|
||||
private val shortNames = fqNames.map { it.shortName().identifier }.toSet()
|
||||
|
||||
private fun CallableDescriptor.isMutatorCall(): Boolean {
|
||||
if (fqNameOrNull() in fqNames) return true
|
||||
return overriddenDescriptors.any { it.isMutatorCall() }
|
||||
}
|
||||
|
||||
fun isNeededCall(expression: KtExpression, resolutionFacade: ResolutionFacade): Boolean {
|
||||
val shortName = when (expression) {
|
||||
is KtCallExpression -> expression.calleeExpression?.text
|
||||
is KtReferenceExpression -> expression.text
|
||||
else -> null
|
||||
} ?: return false
|
||||
if (shortName !in shortNames) return false
|
||||
val call = expression.resolveToCall(resolutionFacade) ?: return false
|
||||
return call.candidateDescriptor.isMutatorCall()
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.inference.mutability
|
||||
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns.FQ_NAMES
|
||||
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.BoundType
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.BoundTypeCalculatorImpl
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.BoundTypeEnhancer
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.InferenceContext
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtQualifiedExpression
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
class MutabilityBoundTypeCalculator(
|
||||
private val resolutionFacade: ResolutionFacade,
|
||||
enhancer: BoundTypeEnhancer
|
||||
) : BoundTypeCalculatorImpl(resolutionFacade, enhancer) {
|
||||
|
||||
private val callResolver = CallResolver(PRESERVING_MUTABILITY_FQ_NAMES)
|
||||
|
||||
override fun interceptCalculateBoundType(inferenceContext: InferenceContext, expression: KtExpression): BoundType? {
|
||||
return when (expression) {
|
||||
is KtQualifiedExpression -> {
|
||||
val selector = expression.selectorExpression ?: return null
|
||||
if (callResolver.isNeededCall(selector, resolutionFacade))
|
||||
expression.receiverExpression.boundType(inferenceContext)
|
||||
else null
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val PRESERVING_MUTABILITY_FQ_NAMES = setOf(
|
||||
FQ_NAMES.collection.child(Name.identifier("iterator")),
|
||||
FQ_NAMES.list.child(Name.identifier("listIterator")),
|
||||
FQ_NAMES.map.child(Name.identifier("entries")),
|
||||
FQ_NAMES.map.child(Name.identifier("values"))
|
||||
)
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.inference.mutability
|
||||
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.*
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.isFlexible
|
||||
|
||||
class MutabilityBoundTypeEnhancer : BoundTypeEnhancer() {
|
||||
override fun enhance(
|
||||
expression: KtExpression,
|
||||
boundType: BoundType,
|
||||
inferenceContext: InferenceContext
|
||||
): BoundType = boundType
|
||||
|
||||
override fun enhanceKotlinType(
|
||||
type: KotlinType,
|
||||
boundType: BoundType,
|
||||
allowLowerEnhancement: Boolean,
|
||||
inferenceContext: InferenceContext
|
||||
): BoundType {
|
||||
if (type.isFlexible()) return boundType
|
||||
val fqName = type.constructor.declarationDescriptor?.fqNameOrNull()
|
||||
val enhancement = when {
|
||||
fqName in MutabilityStateUpdater.mutableToImmutable && allowLowerEnhancement -> State.LOWER
|
||||
fqName in MutabilityStateUpdater.immutableToMutable -> State.UPPER
|
||||
else -> null
|
||||
}
|
||||
return boundType.enhanceWith(enhancement)
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.inference.mutability
|
||||
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.*
|
||||
|
||||
class MutabilityConstraintBoundProvider : ConstraintBoundProviderImpl() {
|
||||
override fun BoundTypeLabel.constraintBound(): ConstraintBound? = when (this) {
|
||||
is TypeVariableLabel -> typeVariable.constraintBound()
|
||||
is TypeParameterLabel -> null
|
||||
is GenericLabel -> null
|
||||
StarProjectionLabel -> null
|
||||
NullLiteralLabel -> null
|
||||
LiteralLabel -> null
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.inference.mutability
|
||||
|
||||
import com.intellij.psi.CommonClassNames
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns.FQ_NAMES
|
||||
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.*
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.collectors.ConstraintsCollector
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtQualifiedExpression
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
class MutabilityConstraintsCollector : ConstraintsCollector() {
|
||||
private val callResolver = CallResolver(MUTATOR_CALL_FQ_NAMES)
|
||||
|
||||
override fun ConstraintBuilder.collectConstraints(
|
||||
element: KtElement,
|
||||
boundTypeCalculator: BoundTypeCalculator,
|
||||
inferenceContext: InferenceContext,
|
||||
resolutionFacade: ResolutionFacade
|
||||
) {
|
||||
when (element) {
|
||||
is KtQualifiedExpression -> {
|
||||
val callExpression = element.selectorExpression?.safeAs<KtCallExpression>() ?: return
|
||||
if (callResolver.isNeededCall(callExpression, resolutionFacade)) {
|
||||
element.receiverExpression.isTheSameTypeAs(State.LOWER, ConstraintPriority.USE_AS_RECEIVER)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val MUTATOR_CALL_FQ_NAMES = setOf(
|
||||
FQ_NAMES.mutableCollection.child(Name.identifier("add")),
|
||||
FQ_NAMES.mutableCollection.child(Name.identifier("addAll")),
|
||||
FQ_NAMES.mutableCollection.child(Name.identifier("remove")),
|
||||
FQ_NAMES.mutableCollection.child(Name.identifier("removeAll")),
|
||||
FQ_NAMES.mutableCollection.child(Name.identifier("retainAll")),
|
||||
FQ_NAMES.mutableCollection.child(Name.identifier("clear")),
|
||||
FQ_NAMES.mutableCollection.child(Name.identifier("removeIf")),
|
||||
|
||||
FQ_NAMES.mutableList.child(Name.identifier("addAll")),
|
||||
FQ_NAMES.mutableList.child(Name.identifier("set")),
|
||||
|
||||
FQ_NAMES.mutableMap.child(Name.identifier("put")),
|
||||
FQ_NAMES.mutableMap.child(Name.identifier("remove")),
|
||||
FQ_NAMES.mutableMap.child(Name.identifier("putAll")),
|
||||
FQ_NAMES.mutableMap.child(Name.identifier("clear")),
|
||||
FQ_NAMES.mutableMap.child(Name.identifier("putIfAbsent")),
|
||||
FQ_NAMES.mutableMap.child(Name.identifier("replace")),
|
||||
FQ_NAMES.mutableMap.child(Name.identifier("replaceAll")),
|
||||
FQ_NAMES.mutableMap.child(Name.identifier("computeIfAbsent")),
|
||||
FQ_NAMES.mutableMap.child(Name.identifier("computeIfPresent")),
|
||||
FQ_NAMES.mutableMap.child(Name.identifier("compute")),
|
||||
FQ_NAMES.mutableMap.child(Name.identifier("merge")),
|
||||
|
||||
FQ_NAMES.mutableMapEntry.child(Name.identifier("setValue")),
|
||||
|
||||
FQ_NAMES.mutableIterator.child(Name.identifier("remove")),
|
||||
|
||||
FQ_NAMES.mutableListIterator.child(Name.identifier("set")),
|
||||
FQ_NAMES.mutableListIterator.child(Name.identifier("add"))
|
||||
)
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.inference.mutability
|
||||
|
||||
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
||||
import org.jetbrains.kotlin.nj2k.NewJ2kConverterContext
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.ClassReference
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.ContextCollector
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.State
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.hasUnknownLabel
|
||||
import org.jetbrains.kotlin.psi.KtTypeElement
|
||||
|
||||
class MutabilityContextCollector(
|
||||
resolutionFacade: ResolutionFacade,
|
||||
private val converterContext: NewJ2kConverterContext
|
||||
) : ContextCollector(resolutionFacade) {
|
||||
override fun ClassReference.getState(typeElement: KtTypeElement?): State = when {
|
||||
typeElement == null -> State.UNUSED
|
||||
typeElement.hasUnknownLabel(converterContext) { it.unknownMutability } -> State.UNKNOWN
|
||||
else -> State.UNUSED
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.inference.mutability
|
||||
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.DefaultStateProvider
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.State
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.TypeVariable
|
||||
|
||||
class MutabilityDefaultStateProvider : DefaultStateProvider() {
|
||||
override fun defaultStateFor(typeVariable: TypeVariable): State =
|
||||
State.UPPER
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.inference.mutability
|
||||
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.State
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.StateUpdater
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.TypeElementBasedTypeVariable
|
||||
import org.jetbrains.kotlin.psi.KtNullableType
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.psi.KtTypeElement
|
||||
import org.jetbrains.kotlin.psi.KtUserType
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.SimpleType
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
class MutabilityStateUpdater : StateUpdater() {
|
||||
override fun TypeElementBasedTypeVariable.updateState() = when (state) {
|
||||
State.LOWER -> changeState(toMutable = true)
|
||||
State.UPPER -> changeState(toMutable = false)
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun TypeElementBasedTypeVariable.changeState(toMutable: Boolean) {
|
||||
changeState(typeElement.typeElement, typeElement.type, toMutable)
|
||||
}
|
||||
|
||||
fun changeState(typeElement: KtTypeElement, type: KotlinType, toMutable: Boolean) {
|
||||
if (type !is SimpleType) return
|
||||
val userTypeElement = when (typeElement) {
|
||||
is KtUserType -> typeElement
|
||||
is KtNullableType -> typeElement.innerType as? KtUserType
|
||||
else -> null
|
||||
} ?: return
|
||||
val initialFqName = type.constructor
|
||||
.declarationDescriptor
|
||||
.safeAs<ClassDescriptor>()
|
||||
?.fqNameOrNull()
|
||||
?: return
|
||||
val newFqName = when {
|
||||
!toMutable && initialFqName in mutableToImmutable -> mutableToImmutable[initialFqName]
|
||||
toMutable && initialFqName in immutableToMutable -> immutableToMutable[initialFqName]
|
||||
else -> null
|
||||
} ?: return
|
||||
val factory = KtPsiFactory(typeElement)
|
||||
userTypeElement.referenceExpression?.replace(factory.createSimpleName(newFqName.shortName().identifier))
|
||||
userTypeElement.qualifier.safeAs<KtUserType>()?.replace(factory.createType(newFqName.parent().asString()).typeElement ?: return)
|
||||
}
|
||||
|
||||
val mutableToImmutable = mapOf(
|
||||
KotlinBuiltIns.FQ_NAMES.mutableIterator to KotlinBuiltIns.FQ_NAMES.iterator,
|
||||
KotlinBuiltIns.FQ_NAMES.mutableCollection to KotlinBuiltIns.FQ_NAMES.collection,
|
||||
KotlinBuiltIns.FQ_NAMES.mutableList to KotlinBuiltIns.FQ_NAMES.list,
|
||||
KotlinBuiltIns.FQ_NAMES.mutableListIterator to KotlinBuiltIns.FQ_NAMES.listIterator,
|
||||
KotlinBuiltIns.FQ_NAMES.mutableSet to KotlinBuiltIns.FQ_NAMES.set,
|
||||
KotlinBuiltIns.FQ_NAMES.mutableMap to KotlinBuiltIns.FQ_NAMES.map,
|
||||
KotlinBuiltIns.FQ_NAMES.mutableMapEntry to KotlinBuiltIns.FQ_NAMES.mapEntry
|
||||
)
|
||||
|
||||
val immutableToMutable = mutableToImmutable.map { (key, value) -> value to key }.toMap()
|
||||
}
|
||||
}
|
||||
+11
-13
@@ -30,19 +30,17 @@ class NullabilityBoundTypeEnhancer(private val resolutionFacade: ResolutionFacad
|
||||
expression: KtExpression,
|
||||
boundType: BoundType,
|
||||
inferenceContext: InferenceContext
|
||||
): BoundType {
|
||||
return when {
|
||||
expression.isNullExpression() ->
|
||||
WithForcedStateBoundType(boundType, State.UPPER)
|
||||
expression is KtCallExpression -> enhanceCallExpression(expression, boundType, inferenceContext)
|
||||
expression is KtQualifiedExpression && expression.selectorExpression is KtCallExpression ->
|
||||
enhanceCallExpression(expression.selectorExpression as KtCallExpression, boundType, inferenceContext)
|
||||
expression is KtNameReferenceExpression ->
|
||||
boundType.enhanceWith(expression.smartCastEnhancement())
|
||||
expression is KtLambdaExpression ->
|
||||
WithForcedStateBoundType(boundType, State.LOWER)
|
||||
else -> boundType
|
||||
}
|
||||
): BoundType = when {
|
||||
expression.isNullExpression() ->
|
||||
WithForcedStateBoundType(boundType, State.UPPER)
|
||||
expression is KtCallExpression -> enhanceCallExpression(expression, boundType, inferenceContext)
|
||||
expression is KtQualifiedExpression && expression.selectorExpression is KtCallExpression ->
|
||||
enhanceCallExpression(expression.selectorExpression as KtCallExpression, boundType, inferenceContext)
|
||||
expression is KtNameReferenceExpression ->
|
||||
boundType.enhanceWith(expression.smartCastEnhancement())
|
||||
expression is KtLambdaExpression ->
|
||||
WithForcedStateBoundType(boundType, State.LOWER)
|
||||
else -> boundType
|
||||
}
|
||||
|
||||
private fun enhanceCallExpression(
|
||||
|
||||
+4
-5
@@ -7,20 +7,19 @@ package org.jetbrains.kotlin.nj2k.inference.nullability
|
||||
|
||||
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
||||
import org.jetbrains.kotlin.nj2k.NewJ2kConverterContext
|
||||
import org.jetbrains.kotlin.nj2k.UnknownNullability
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.*
|
||||
import org.jetbrains.kotlin.psi.KtNullableType
|
||||
import org.jetbrains.kotlin.psi.KtTypeElement
|
||||
import org.jetbrains.kotlin.psi.KtTypeReference
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
import org.jetbrains.kotlin.types.typeUtil.isUnit
|
||||
|
||||
class NullabilityContextCollector(
|
||||
resolutionFacade: ResolutionFacade,
|
||||
private val converterContext: NewJ2kConverterContext
|
||||
) : ContextCollector(resolutionFacade) {
|
||||
override fun ClassReference.getState(typeElement: KtTypeElement?): State? = when {
|
||||
override fun ClassReference.getState(typeElement: KtTypeElement?): State = when {
|
||||
descriptor?.defaultType?.isUnit() == true -> State.LOWER
|
||||
typeElement == null -> State.UNKNOWN
|
||||
typeElement.hasUnknownLabel(converterContext, UnknownNullability) -> State.UNKNOWN
|
||||
typeElement.hasUnknownLabel(converterContext) { it.unknownNullability } -> State.UNKNOWN
|
||||
typeElement is KtNullableType -> State.UPPER
|
||||
else -> State.LOWER
|
||||
}
|
||||
|
||||
+4
-9
@@ -5,23 +5,18 @@
|
||||
|
||||
package org.jetbrains.kotlin.nj2k.inference.nullability
|
||||
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.*
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.State
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.StateUpdater
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.TypeElementBasedTypeVariable
|
||||
import org.jetbrains.kotlin.psi.KtNullableType
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.psi.KtTypeElement
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
|
||||
class NullabilityStateUpdater : StateUpdater() {
|
||||
override fun TypeElementBasedTypeVariable.updateState() = when (state) {
|
||||
State.LOWER -> changeState(toNullable = false)
|
||||
State.UPPER -> changeState(toNullable = true)
|
||||
State.UNKNOWN -> {
|
||||
if (typeElement is TypeParameterElementData) {
|
||||
changeState(toNullable = false)
|
||||
} else {
|
||||
changeState(toNullable = true)
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
private fun TypeElementBasedTypeVariable.changeState(toNullable: Boolean) {
|
||||
|
||||
@@ -245,8 +245,9 @@ private val processings: List<NamedPostProcessingGroup> = listOf(
|
||||
),
|
||||
runSingleTime = true
|
||||
),
|
||||
nullabilityProcessing,
|
||||
clearUndefinedLabelsProcessing
|
||||
NullabilityInferenceProcessing(),
|
||||
MutabilityInferenceProcessing(),
|
||||
clearUnknownLabelsProcessing
|
||||
)
|
||||
),
|
||||
NamedPostProcessingGroup(
|
||||
@@ -255,7 +256,7 @@ private val processings: List<NamedPostProcessingGroup> = listOf(
|
||||
),
|
||||
NamedPostProcessingGroup(
|
||||
"Shortening fully-qualified references",
|
||||
listOf(shortenReferencesProcessing)
|
||||
listOf(ShortenReferenceProcessing())
|
||||
),
|
||||
NamedPostProcessingGroup(
|
||||
"Converting POJOs to data classes",
|
||||
@@ -285,7 +286,7 @@ private val processings: List<NamedPostProcessingGroup> = listOf(
|
||||
"Optimizing imports",
|
||||
listOf(
|
||||
optimizeImportsProcessing,
|
||||
shortenReferencesProcessing
|
||||
ShortenReferenceProcessing()
|
||||
)
|
||||
),
|
||||
NamedPostProcessingGroup(
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.postProcessing.processings
|
||||
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.editor.RangeMarker
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.core.util.EDT
|
||||
import org.jetbrains.kotlin.idea.core.util.range
|
||||
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.nj2k.NewJ2kConverterContext
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.BoundTypeCalculatorImpl
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.ByInfoSuperFunctionsProvider
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.ConstraintsCollectorAggregator
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.InferenceFacade
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.collectors.CallExpressionConstraintCollector
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.collectors.CommonConstraintsCollector
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.collectors.FunctionConstraintsCollector
|
||||
import org.jetbrains.kotlin.nj2k.inference.mutability.*
|
||||
import org.jetbrains.kotlin.nj2k.inference.nullability.*
|
||||
import org.jetbrains.kotlin.nj2k.postProcessing.GeneralPostProcessing
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.psiUtil.elementsInRange
|
||||
|
||||
abstract class InferenceProcessing : GeneralPostProcessing {
|
||||
final override suspend fun runProcessing(file: KtFile, rangeMarker: RangeMarker?, converterContext: NewJ2kConverterContext) {
|
||||
val elements = if (rangeMarker != null) {
|
||||
val range = rangeMarker.range ?: return
|
||||
runReadAction { file.elementsInRange(range) }.filterIsInstance<KtElement>()
|
||||
} else listOf(file)
|
||||
val resolutionFacade = runReadAction { file.getResolutionFacade() }
|
||||
|
||||
withContext(EDT) {
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
createInferenceFacade(resolutionFacade, converterContext).runOn(elements)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun createInferenceFacade(
|
||||
resolutionFacade: ResolutionFacade,
|
||||
converterContext: NewJ2kConverterContext
|
||||
): InferenceFacade
|
||||
}
|
||||
|
||||
class NullabilityInferenceProcessing : InferenceProcessing() {
|
||||
override fun createInferenceFacade(
|
||||
resolutionFacade: ResolutionFacade,
|
||||
converterContext: NewJ2kConverterContext
|
||||
): InferenceFacade = InferenceFacade(
|
||||
NullabilityContextCollector(resolutionFacade, converterContext),
|
||||
ConstraintsCollectorAggregator(
|
||||
resolutionFacade,
|
||||
NullabilityConstraintBoundProvider(),
|
||||
listOf(
|
||||
CommonConstraintsCollector(),
|
||||
CallExpressionConstraintCollector(),
|
||||
FunctionConstraintsCollector(ByInfoSuperFunctionsProvider(resolutionFacade, converterContext)),
|
||||
NullabilityConstraintsCollector()
|
||||
)
|
||||
),
|
||||
BoundTypeCalculatorImpl(resolutionFacade, NullabilityBoundTypeEnhancer(resolutionFacade)),
|
||||
NullabilityStateUpdater(),
|
||||
NullabilityDefaultStateProvider()
|
||||
)
|
||||
}
|
||||
|
||||
class MutabilityInferenceProcessing : InferenceProcessing() {
|
||||
override fun createInferenceFacade(
|
||||
resolutionFacade: ResolutionFacade,
|
||||
converterContext: NewJ2kConverterContext
|
||||
): InferenceFacade = InferenceFacade(
|
||||
MutabilityContextCollector(resolutionFacade, converterContext),
|
||||
ConstraintsCollectorAggregator(
|
||||
resolutionFacade,
|
||||
MutabilityConstraintBoundProvider(),
|
||||
listOf(
|
||||
CommonConstraintsCollector(),
|
||||
CallExpressionConstraintCollector(),
|
||||
FunctionConstraintsCollector(ByInfoSuperFunctionsProvider(resolutionFacade, converterContext)),
|
||||
MutabilityConstraintsCollector()
|
||||
)
|
||||
),
|
||||
MutabilityBoundTypeCalculator(resolutionFacade, MutabilityBoundTypeEnhancer()),
|
||||
MutabilityStateUpdater(),
|
||||
MutabilityDefaultStateProvider()
|
||||
)
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.postProcessing.processings
|
||||
|
||||
import com.intellij.openapi.editor.RangeMarker
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.nj2k.ImportStorage
|
||||
import org.jetbrains.kotlin.nj2k.NewJ2kConverterContext
|
||||
import org.jetbrains.kotlin.nj2k.postProcessing.SimplePostProcessing
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtQualifiedExpression
|
||||
|
||||
class ShortenReferenceProcessing : SimplePostProcessing() {
|
||||
private val filter = filter@{ element: PsiElement ->
|
||||
when (element) {
|
||||
is KtQualifiedExpression -> {
|
||||
val fqName = element.selectorExpression?.mainReference?.resolve()?.getKotlinFqName()
|
||||
?: return@filter ShortenReferences.FilterResult.PROCESS
|
||||
if (ImportStorage.isImportNeeded(fqName)) ShortenReferences.FilterResult.PROCESS
|
||||
else ShortenReferences.FilterResult.SKIP
|
||||
}
|
||||
else -> ShortenReferences.FilterResult.PROCESS
|
||||
}
|
||||
}
|
||||
|
||||
override fun applySimpleProcessing(file: KtFile, rangeMarker: RangeMarker?, converterContext: NewJ2kConverterContext) {
|
||||
if (rangeMarker != null) {
|
||||
if (rangeMarker.isValid) {
|
||||
ShortenReferences.DEFAULT.process(file, rangeMarker.startOffset, rangeMarker.endOffset, filter)
|
||||
}
|
||||
} else {
|
||||
ShortenReferences.DEFAULT.process(file, filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-61
@@ -10,29 +10,12 @@ import com.intellij.psi.PsiComment
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.core.util.range
|
||||
import org.jetbrains.kotlin.idea.formatter.commitAndUnblockDocument
|
||||
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.nj2k.ImportStorage
|
||||
import org.jetbrains.kotlin.nj2k.asLabel
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.BoundTypeCalculatorImpl
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.ByInfoSuperFunctionsProvider
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.ConstraintsCollectorAggregator
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.InferenceFacade
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.collectors.CallExpressionConstraintCollector
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.collectors.CommonConstraintsCollector
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.collectors.FunctionConstraintsCollector
|
||||
import org.jetbrains.kotlin.nj2k.inference.nullability.NullabilityBoundTypeEnhancer
|
||||
import org.jetbrains.kotlin.nj2k.inference.nullability.NullabilityConstraintsCollector
|
||||
import org.jetbrains.kotlin.nj2k.inference.nullability.NullabilityContextCollector
|
||||
import org.jetbrains.kotlin.nj2k.inference.nullability.NullabilityStateUpdater
|
||||
import org.jetbrains.kotlin.nj2k.postProcessing.postProcessing
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.elementsInRange
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
val formatCodeProcessing =
|
||||
postProcessing { file, rangeMarker, _ ->
|
||||
@@ -47,33 +30,7 @@ val formatCodeProcessing =
|
||||
}
|
||||
}
|
||||
|
||||
val nullabilityProcessing =
|
||||
postProcessing { file, rangeMarker, converterContext ->
|
||||
val resolutionFacade = file.getResolutionFacade()
|
||||
val inferenceFacade = InferenceFacade(
|
||||
NullabilityContextCollector(resolutionFacade, converterContext),
|
||||
ConstraintsCollectorAggregator(
|
||||
resolutionFacade,
|
||||
NullabilityConstraintBoundProvider(),
|
||||
listOf(
|
||||
CommonConstraintsCollector(),
|
||||
CallExpressionConstraintCollector(),
|
||||
FunctionConstraintsCollector(ByInfoSuperFunctionsProvider(resolutionFacade, converterContext)),
|
||||
NullabilityConstraintsCollector()
|
||||
)
|
||||
),
|
||||
BoundTypeCalculatorImpl(resolutionFacade, NullabilityBoundTypeEnhancer(resolutionFacade)),
|
||||
NullabilityStateUpdater(),
|
||||
NullabilityDefaultStateProvider()
|
||||
)
|
||||
val elements = if (rangeMarker != null) {
|
||||
file.elementsInRange(rangeMarker.range ?: return@postProcessing).filterIsInstance<KtElement>()
|
||||
} else listOf(file)
|
||||
|
||||
inferenceFacade.runOn(elements)
|
||||
}
|
||||
|
||||
val clearUndefinedLabelsProcessing =
|
||||
val clearUnknownLabelsProcessing =
|
||||
postProcessing { file, _, _ ->
|
||||
file.clearUndefinedLabels()
|
||||
}
|
||||
@@ -94,23 +51,6 @@ private fun KtFile.clearUndefinedLabels() {
|
||||
comments.forEach { it.delete() }
|
||||
}
|
||||
|
||||
val shortenReferencesProcessing =
|
||||
postProcessing { file, rangeMarker, _ ->
|
||||
val filter = filter@{ element: PsiElement ->
|
||||
if (element !is KtQualifiedExpression) return@filter ShortenReferences.FilterResult.PROCESS
|
||||
val fqName = element.selectorExpression?.mainReference?.resolve()?.getKotlinFqName()
|
||||
?: return@filter ShortenReferences.FilterResult.PROCESS
|
||||
if (ImportStorage.isImportNeeded(fqName)) ShortenReferences.FilterResult.PROCESS
|
||||
else ShortenReferences.FilterResult.SKIP
|
||||
}
|
||||
if (rangeMarker != null) {
|
||||
if (rangeMarker.isValid) {
|
||||
ShortenReferences.DEFAULT.process(file, rangeMarker.startOffset, rangeMarker.endOffset, filter)
|
||||
}
|
||||
} else {
|
||||
ShortenReferences.DEFAULT.process(file, filter)
|
||||
}
|
||||
}
|
||||
|
||||
val optimizeImportsProcessing =
|
||||
postProcessing { file, rangeMarker, _ ->
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
import java.io.File
|
||||
|
||||
|
||||
private val JK_ROOT = File("./nj2k/src/org/jetbrains/kotlin/nj2k/tree")
|
||||
|
||||
private val JK_OUT_ROOT = File(JK_ROOT, "visitors")
|
||||
|
||||
private val JK_TREE_FILES = listOf(
|
||||
"declarations.kt",
|
||||
"elements.kt",
|
||||
"expressions.kt",
|
||||
"modifiers.kt",
|
||||
"statements.kt"
|
||||
).map { File(JK_ROOT, it) }
|
||||
|
||||
|
||||
val elementRegex = """(class|object)\s+(JK[\w]+)(\([\w\s:,<>=\(\)\\]+\))?\s*:\s*(JK[a-zA-Z]+)""".toRegex()
|
||||
|
||||
|
||||
data class InterfaceData(val name: String, val extends: String?)
|
||||
|
||||
fun File.interfaceNames() =
|
||||
sequenceOf(this)
|
||||
.map { it.readText() }
|
||||
.flatMap { elementRegex.findAll(it) }
|
||||
.map { match ->
|
||||
InterfaceData(
|
||||
match.groupValues[2],
|
||||
match.groupValues[4].let { if (it == "JKAnnotationMemberValue") "JKTreeElement" else it }
|
||||
)
|
||||
}.toList()
|
||||
|
||||
|
||||
fun String.safeVarName() = when (this) {
|
||||
"class" -> "klass"
|
||||
else -> this
|
||||
}
|
||||
|
||||
fun genVisitors(
|
||||
interfaceData: List<InterfaceData>,
|
||||
visitorName: String
|
||||
) {
|
||||
val pkg = "package org.jetbrains.kotlin.nj2k.tree.visitors"
|
||||
|
||||
File(JK_OUT_ROOT, "$visitorName.kt").writeText(buildString {
|
||||
appendln(pkg)
|
||||
appendln()
|
||||
appendln("import org.jetbrains.kotlin.nj2k.tree.*")
|
||||
appendln()
|
||||
appendln("abstract class $visitorName {")
|
||||
interfaceData.joinTo(this, separator = "\n") { (name, ext) ->
|
||||
val nameWithoutPrefix = name.removePrefix("JK")
|
||||
val argName = nameWithoutPrefix.decapitalize().safeVarName()
|
||||
val generifyCall = if (name != "JKTreeElement") "= visit${ext?.removePrefix("JK") ?: error(name)}($argName)" else ""
|
||||
val modifier = if (name == "JKTreeElement") "abstract" else "open"
|
||||
"""
|
||||
| $modifier fun visit$nameWithoutPrefix($argName: $name) $generifyCall
|
||||
""".trimMargin()
|
||||
}
|
||||
appendln()
|
||||
appendln("}")
|
||||
})
|
||||
|
||||
|
||||
File(JK_OUT_ROOT, "${visitorName}WithCommentsPrinting.kt").writeText(buildString {
|
||||
appendln(pkg)
|
||||
appendln()
|
||||
appendln("import org.jetbrains.kotlin.nj2k.tree.*")
|
||||
appendln()
|
||||
|
||||
appendln("abstract class ${visitorName}WithCommentsPrinting : $visitorName() {")
|
||||
appendln(
|
||||
"""
|
||||
| abstract fun printLeftNonCodeElements(element: JKNonCodeElementsListOwner)
|
||||
| abstract fun printRightNonCodeElements(element: JKNonCodeElementsListOwner)
|
||||
|
|
||||
""".trimMargin()
|
||||
)
|
||||
|
||||
interfaceData.joinTo(this, separator = "\n\n") { (name, ext) ->
|
||||
val nameWithoutPrefix = name.removePrefix("JK")
|
||||
val argName = nameWithoutPrefix.decapitalize().safeVarName()
|
||||
val arg = "$argName: $name"
|
||||
val rawVisitSuffix = "Raw"
|
||||
val generifyCall = if (name != "JKTreeElement") "= visit${ext!!.removePrefix("JK")}$rawVisitSuffix($argName)" else ""
|
||||
val modifier = if (name == "JKTreeElement") "abstract" else "open"
|
||||
"""
|
||||
| final override fun visit$nameWithoutPrefix($arg) {
|
||||
| printLeftNonCodeElements($argName)
|
||||
| visit$nameWithoutPrefix$rawVisitSuffix($argName)
|
||||
| printRightNonCodeElements($argName)
|
||||
| }
|
||||
|
|
||||
| $modifier fun visit$nameWithoutPrefix$rawVisitSuffix($arg) $generifyCall
|
||||
""".trimMargin()
|
||||
}
|
||||
appendln()
|
||||
appendln("}")
|
||||
})
|
||||
}
|
||||
|
||||
fun main() {
|
||||
genVisitors(JK_TREE_FILES.flatMap { it.interfaceNames() }, "JKVisitor")
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
package org.jetbrains.kotlin.nj2k
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
|
||||
import org.jetbrains.kotlin.utils.SmartList
|
||||
import kotlin.random.Random
|
||||
|
||||
interface JKElementInfo
|
||||
|
||||
@@ -16,8 +17,23 @@ data class InternalSuperFunctionInfo(val label: JKElementInfoLabel) : SuperFunct
|
||||
|
||||
data class FunctionInfo(val superFunctions: List<SuperFunctionInfo>) : JKElementInfo
|
||||
|
||||
object UnknownNullability : JKElementInfo
|
||||
object UnknownMutability : JKElementInfo
|
||||
enum class JKTypeInfo(val unknownNullability: Boolean, val unknownMutability: Boolean) : JKElementInfo {
|
||||
KNOWN_NULLABILITY_KNOWN_MUTABILITY(false, false),
|
||||
UNKNOWN_NULLABILITY_KNOWN_MUTABILITY(true, false),
|
||||
KNOWN_NULLABILITY_UNKNOWN_MUTABILITY(false, true),
|
||||
UNKNOWN_NULLABILITY_UNKNOWN_MUTABILITY(true, true)
|
||||
;
|
||||
|
||||
companion object {
|
||||
operator fun invoke(unknownNullability: Boolean, unknownMutability: Boolean) = when {
|
||||
!unknownNullability && !unknownMutability -> KNOWN_NULLABILITY_KNOWN_MUTABILITY
|
||||
unknownNullability && !unknownMutability -> UNKNOWN_NULLABILITY_KNOWN_MUTABILITY
|
||||
!unknownNullability && unknownMutability -> KNOWN_NULLABILITY_UNKNOWN_MUTABILITY
|
||||
else -> UNKNOWN_NULLABILITY_UNKNOWN_MUTABILITY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inline class JKElementInfoLabel(val label: String) {
|
||||
fun render(): String = "/*@@$label@@*/"
|
||||
@@ -31,22 +47,18 @@ 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 labelToInfo = mutableMapOf<JKElementInfoLabel, MutableList<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
|
||||
labelToInfo.getOrPut(label) { SmartList() } += info
|
||||
elementToLabel[element] = label
|
||||
}
|
||||
|
||||
@@ -54,11 +66,8 @@ class JKElementInfoStorage {
|
||||
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("")
|
||||
private fun createRandomString() = (1..generatedStringLength).joinToString("") {
|
||||
charPool[Random.nextInt(0, charPool.size)].toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,22 +6,22 @@
|
||||
package org.jetbrains.kotlin.nj2k.conversions
|
||||
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.psi.impl.light.LightMethod
|
||||
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
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.load.java.descriptors.JavaMethodDescriptor
|
||||
import org.jetbrains.kotlin.nj2k.*
|
||||
import org.jetbrains.kotlin.nj2k.symbols.JKUniverseMethodSymbol
|
||||
import org.jetbrains.kotlin.nj2k.tree.*
|
||||
import org.jetbrains.kotlin.nj2k.tree.impl.JKCapturedType
|
||||
import org.jetbrains.kotlin.nj2k.tree.impl.psi
|
||||
import org.jetbrains.kotlin.nj2k.types.JKParametrizedType
|
||||
import org.jetbrains.kotlin.nj2k.types.JKStarProjectionType
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
class AddElementsInfoConversion(private val context: NewJ2kConverterContext) : RecursiveApplicableConversionBase() {
|
||||
class AddElementsInfoConversion(context: NewJ2kConverterContext) : RecursiveApplicableConversionBase(context) {
|
||||
override fun applyToElement(element: JKTreeElement): JKTreeElement {
|
||||
when (element) {
|
||||
is JKTypeElement -> addInfoForTypeElement(element)
|
||||
@@ -33,14 +33,10 @@ class AddElementsInfoConversion(private val context: NewJ2kConverterContext) : R
|
||||
|
||||
private fun addInfoForTypeElement(typeElement: JKTypeElement) {
|
||||
typeElement.type.forAllInnerTypes { type ->
|
||||
if (type.nullability == Nullability.Default
|
||||
|| type.safeAs<JKCapturedType>()?.wildcardType is JKStarProjectionType
|
||||
) {
|
||||
context.elementsInfoStorage.addEntry(type, UnknownNullability)
|
||||
}
|
||||
if (type.isCollectionType) {
|
||||
context.elementsInfoStorage.addEntry(type, UnknownMutability)
|
||||
}
|
||||
val hasUnknownNullability =
|
||||
type.nullability == Nullability.Default || type.safeAs<JKCapturedType>()?.wildcardType is JKStarProjectionType
|
||||
val hasUnknownMutability = type.isCollectionType
|
||||
context.elementsInfoStorage.addEntry(type, JKTypeInfo(hasUnknownNullability, hasUnknownMutability))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +44,7 @@ class AddElementsInfoConversion(private val context: NewJ2kConverterContext) : R
|
||||
val superMethods = function.superMethods() ?: return
|
||||
val superDescriptorsInfo = superMethods.map { superDescriptor ->
|
||||
val superPsi = superDescriptor.original.findPsi()
|
||||
when (val symbol = context.symbolProvider.symbolsByPsi[superPsi]) {
|
||||
when (val symbol = symbolProvider.symbolsByPsi[superPsi]) {
|
||||
is JKUniverseMethodSymbol ->
|
||||
InternalSuperFunctionInfo(context.elementsInfoStorage.getOrCreateInfoForElement(symbol.target))
|
||||
else -> ExternalSuperFunctionInfo(superDescriptor)
|
||||
@@ -59,8 +55,8 @@ class AddElementsInfoConversion(private val context: NewJ2kConverterContext) : R
|
||||
|
||||
private fun JKMethod.superMethods(): Collection<FunctionDescriptor>? {
|
||||
val psiMethod = psi<PsiMethod>() ?: return null
|
||||
psiMethod.getJavaMethodDescriptor()?.let { descriptor ->
|
||||
return descriptor.overriddenDescriptors
|
||||
psiMethod.getJavaMethodDescriptor()?.let {
|
||||
return it.overriddenDescriptors
|
||||
}
|
||||
return psiMethod.findSuperMethods().mapNotNull { superMethod ->
|
||||
when (superMethod) {
|
||||
|
||||
@@ -112,6 +112,7 @@ class TypeMappingConversion(val context: NewJ2kConverterContext) : RecursiveAppl
|
||||
|
||||
private fun JKClassSymbol.mapClassSymbol(typeElement: JKTypeElement?): JKClassSymbol {
|
||||
if (this is JKUniverseClassSymbol) return this
|
||||
if (typeElement?.parentOfType<JKInheritanceInfo>() != null) return this
|
||||
val newFqName = kotlinCollectionClassName()
|
||||
?: kotlinStandardType()
|
||||
?: fqName
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import java.io.File
|
||||
|
||||
internal fun foo(file: File?): List<String> {
|
||||
return emptyList()
|
||||
internal fun foo(file: File?): List<String?>? {
|
||||
return emptyList<String>()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import java.io.File
|
||||
|
||||
class C {
|
||||
private fun memberFun(file: File?) {}
|
||||
private fun memberFun(file: File) {}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package to
|
||||
|
||||
fun foo(): String {
|
||||
fun foo(): String? {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package to
|
||||
|
||||
@SomeAnnotation
|
||||
fun foo(): String {
|
||||
fun foo(): String? {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@ class A {
|
||||
return JetRefactoringUtil.formatPsiMethod(element as PsiMethod, true, false)
|
||||
}
|
||||
|
||||
protected fun getDimensionServiceKey(): String {
|
||||
protected fun getDimensionServiceKey(): String? {
|
||||
return "#org.jetbrains.kotlin.idea.refactoring.safeDelete.KotlinOverridingDialog"
|
||||
}
|
||||
|
||||
fun getSelected(): ArrayList<UsageInfo> {
|
||||
val result: ArrayList<UsageInfo> = ArrayList<UsageInfo?>()
|
||||
fun getSelected(): ArrayList<UsageInfo>? {
|
||||
val result: ArrayList<UsageInfo> = ArrayList<UsageInfo>()
|
||||
for (i in 0 until myChecked.length) {
|
||||
if (myChecked.get(i)) {
|
||||
result.add(myOverridingMethods.get(i))
|
||||
|
||||
@@ -3,5 +3,5 @@ import java.util.ArrayList
|
||||
|
||||
|
||||
fun foo() {
|
||||
bar(ArrayList<String?>())
|
||||
bar(ArrayList<String>())
|
||||
}
|
||||
@@ -2,4 +2,4 @@
|
||||
import java.util.ArrayList
|
||||
|
||||
|
||||
fun foo() = ArrayList<String?>()
|
||||
fun foo() = ArrayList<String>()
|
||||
+1
-1
@@ -3,5 +3,5 @@ import java.util.ArrayList
|
||||
|
||||
|
||||
fun foo() {
|
||||
bar(/*comment*/ArrayList<String?>())
|
||||
bar(/*comment*/ArrayList<String>())
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import java.util.ArrayList
|
||||
|
||||
|
||||
class JavaClass {
|
||||
internal fun foo(file: File?, target: List<String?>?) {
|
||||
internal fun foo(file: File?, target: MutableList<String>?) {
|
||||
val list = ArrayList<String>()
|
||||
if (file != null) {
|
||||
list.add(file.name)
|
||||
|
||||
+1
-1
@@ -7,4 +7,4 @@ fun test() {
|
||||
|
||||
//T0 <: T1 due to 'INITIALIZER'
|
||||
//T2 <: T4 due to 'INITIALIZER'
|
||||
//T3 <: T1 due to 'ASSIGNMENT'
|
||||
//T1 <: T3 due to 'ASSIGNMENT'
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class Test {
|
||||
val x: /*T2@*/List</*T1@*/Int> = ArrayList</*T0@*/Int>()/*ArrayList<T0@Int>*/
|
||||
}
|
||||
|
||||
//T1 := T0 due to 'INITIALIZER'
|
||||
@@ -0,0 +1,8 @@
|
||||
fun a(
|
||||
l0: /*T1@*/MutableIterator</*T0@*/Int>
|
||||
) {
|
||||
l0/*T1@MutableIterator<T0@Int>*/.remove()
|
||||
}
|
||||
|
||||
//T0 <: T0 due to 'RECEIVER_PARAMETER'
|
||||
//T1 := LOWER due to 'USE_AS_RECEIVER'
|
||||
@@ -0,0 +1,7 @@
|
||||
import java.util.ArrayList
|
||||
|
||||
fun a() {
|
||||
val list: /*T2@*/List</*T1@*/Int> = ArrayList</*T0@*/Int>()/*ArrayList<T0@Int>*/
|
||||
}
|
||||
|
||||
//T1 := T0 due to 'INITIALIZER'
|
||||
@@ -0,0 +1,30 @@
|
||||
fun a(
|
||||
l0: /*T1@*/MutableCollection</*T0@*/Int>,
|
||||
l1: /*T3@*/MutableCollection</*T2@*/Int>,
|
||||
l2: /*T5@*/MutableCollection</*T4@*/Int>,
|
||||
l3: /*T7@*/MutableCollection</*T6@*/Int>,
|
||||
l4: /*T9@*/MutableCollection</*T8@*/Int>
|
||||
) {
|
||||
l0/*T1@MutableCollection<T0@Int>*/.add(1/*LIT*/)
|
||||
l1/*T3@MutableCollection<T2@Int>*/.addAll(l1/*T3@MutableCollection<T2@Int>*/)
|
||||
l2/*T5@MutableCollection<T4@Int>*/.clear()
|
||||
l3/*T7@MutableCollection<T6@Int>*/.removeAll(l1/*T3@MutableCollection<T2@Int>*/)
|
||||
l4/*T9@MutableCollection<T8@Int>*/.retainAll(l1/*T3@MutableCollection<T2@Int>*/)
|
||||
}
|
||||
|
||||
//T0 := T0 due to 'RECEIVER_PARAMETER'
|
||||
//T1 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T2 := T2 due to 'RECEIVER_PARAMETER'
|
||||
//T2 := T2 due to 'PARAMETER'
|
||||
//T3 <: UPPER due to 'PARAMETER'
|
||||
//T3 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T4 := T4 due to 'RECEIVER_PARAMETER'
|
||||
//T5 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T6 := T6 due to 'RECEIVER_PARAMETER'
|
||||
//T6 := T2 due to 'PARAMETER'
|
||||
//T3 <: UPPER due to 'PARAMETER'
|
||||
//T7 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T8 := T8 due to 'RECEIVER_PARAMETER'
|
||||
//T8 := T2 due to 'PARAMETER'
|
||||
//T3 <: UPPER due to 'PARAMETER'
|
||||
//T9 := LOWER due to 'USE_AS_RECEIVER'
|
||||
@@ -0,0 +1,8 @@
|
||||
class Nya {
|
||||
private val hashMap: /*T6@*/Map</*T3@*/String, /*T5@*/List</*T4@*/Int>> =
|
||||
HashMap</*T0@*/String, /*T2@*/List</*T1@*/Int>>()/*HashMap<T0@String, T2@MutableList<T1@Int>>*/
|
||||
}
|
||||
|
||||
//T3 := T0 due to 'INITIALIZER'
|
||||
//T4 := T1 due to 'INITIALIZER'
|
||||
//T5 := T2 due to 'INITIALIZER'
|
||||
@@ -0,0 +1,10 @@
|
||||
fun a(l0: /*T1@*/MutableList</*T0@*/Int>) {
|
||||
val iterator: /*T3@*/MutableIterator</*T2@*/Int> = l0/*T1@MutableList<T0@Int>*/.iterator()/*T1@MutableList<T0@Int>*/
|
||||
iterator/*T3@MutableIterator<T2@Int>*/.remove()
|
||||
}
|
||||
|
||||
//T0 := T0 due to 'RECEIVER_PARAMETER'
|
||||
//T2 := T0 due to 'INITIALIZER'
|
||||
//T1 <: T3 due to 'INITIALIZER'
|
||||
//T2 <: T2 due to 'RECEIVER_PARAMETER'
|
||||
//T3 := LOWER due to 'USE_AS_RECEIVER'
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
class A {
|
||||
val l: /*T1@*/List</*T0@*/Int>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
fun a(
|
||||
l0: /*T1@*/MutableListIterator</*T0@*/Int>,
|
||||
l1: /*T3@*/MutableListIterator</*T2@*/Int>,
|
||||
l2: /*T5@*/MutableListIterator</*T4@*/Int>
|
||||
) {
|
||||
l0/*T1@MutableListIterator<T0@Int>*/.add(1/*LIT*/)
|
||||
l1/*T3@MutableListIterator<T2@Int>*/.remove()
|
||||
l2/*T5@MutableListIterator<T4@Int>*/.set(1/*LIT*/)
|
||||
}
|
||||
|
||||
//T0 := T0 due to 'RECEIVER_PARAMETER'
|
||||
//T1 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T2 := T2 due to 'RECEIVER_PARAMETER'
|
||||
//T3 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T4 := T4 due to 'RECEIVER_PARAMETER'
|
||||
//T5 := LOWER due to 'USE_AS_RECEIVER'
|
||||
@@ -0,0 +1,46 @@
|
||||
fun a(
|
||||
l0: /*T1@*/MutableList</*T0@*/Int>,
|
||||
l1: /*T3@*/MutableList</*T2@*/Int>,
|
||||
l2: /*T5@*/MutableList</*T4@*/Int>,
|
||||
l3: /*T7@*/MutableList</*T6@*/Int>,
|
||||
l4: /*T9@*/MutableList</*T8@*/Int>,
|
||||
l5: /*T11@*/MutableList</*T10@*/Int>,
|
||||
l6: /*T13@*/MutableList</*T12@*/Int>,
|
||||
l7: /*T15@*/MutableList</*T14@*/Int>
|
||||
) {
|
||||
l0/*T1@MutableList<T0@Int>*/.add(1/*LIT*/)
|
||||
l1/*T3@MutableList<T2@Int>*/.addAll(1/*LIT*/, l1/*T3@MutableList<T2@Int>*/)
|
||||
l2/*T5@MutableList<T4@Int>*/.addAll(l1/*T3@MutableList<T2@Int>*/)
|
||||
l3/*T7@MutableList<T6@Int>*/.clear()
|
||||
l4/*T9@MutableList<T8@Int>*/.remove(1/*LIT*/)
|
||||
l5/*T11@MutableList<T10@Int>*/.removeAll(l1/*T3@MutableList<T2@Int>*/)
|
||||
l5/*T11@MutableList<T10@Int>*/.removeAt(0/*LIT*/)
|
||||
l6/*T13@MutableList<T12@Int>*/.retainAll(l1/*T3@MutableList<T2@Int>*/)
|
||||
l7/*T15@MutableList<T14@Int>*/.set(0/*LIT*/, 0/*LIT*/)
|
||||
}
|
||||
|
||||
//T0 := T0 due to 'RECEIVER_PARAMETER'
|
||||
//T1 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T2 := T2 due to 'RECEIVER_PARAMETER'
|
||||
//T2 := T2 due to 'PARAMETER'
|
||||
//T3 <: UPPER due to 'PARAMETER'
|
||||
//T3 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T4 := T4 due to 'RECEIVER_PARAMETER'
|
||||
//T4 := T2 due to 'PARAMETER'
|
||||
//T3 <: UPPER due to 'PARAMETER'
|
||||
//T5 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T6 := T6 due to 'RECEIVER_PARAMETER'
|
||||
//T7 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T8 := T8 due to 'RECEIVER_PARAMETER'
|
||||
//T9 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T10 := T10 due to 'RECEIVER_PARAMETER'
|
||||
//T10 := T2 due to 'PARAMETER'
|
||||
//T3 <: UPPER due to 'PARAMETER'
|
||||
//T11 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T10 := T10 due to 'RECEIVER_PARAMETER'
|
||||
//T12 := T12 due to 'RECEIVER_PARAMETER'
|
||||
//T12 := T2 due to 'PARAMETER'
|
||||
//T3 <: UPPER due to 'PARAMETER'
|
||||
//T13 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T14 := T14 due to 'RECEIVER_PARAMETER'
|
||||
//T15 := LOWER due to 'USE_AS_RECEIVER'
|
||||
@@ -0,0 +1,15 @@
|
||||
import java.util.ArrayList
|
||||
|
||||
fun test() {
|
||||
val list: /*T4@*/List</*T3@*/MutableList</*T2@*/Int>> = ArrayList</*T1@*/MutableList</*T0@*/Int>>()/*ArrayList<T1@MutableList<T0@Int>>*/
|
||||
for(l: /*T6@*/MutableList</*T5@*/Int> in list/*T4@MutableList<T3@MutableList<T2@Int>>*/) {
|
||||
l/*T6@MutableList<T5@Int>*/.add(42/*LIT*/)
|
||||
}
|
||||
}
|
||||
|
||||
//T2 := T0 due to 'INITIALIZER'
|
||||
//T3 := T1 due to 'INITIALIZER'
|
||||
//T5 := T5 due to 'RECEIVER_PARAMETER'
|
||||
//T6 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T5 := T2 due to 'ASSIGNMENT'
|
||||
//T3 <: T6 due to 'ASSIGNMENT'
|
||||
@@ -0,0 +1,14 @@
|
||||
import java.util.ArrayList
|
||||
|
||||
fun a() {
|
||||
val list: /*T4@*/List</*T3@*/MutableList</*T2@*/Int>> = ArrayList</*T1@*/MutableList</*T0@*/Int>>()/*ArrayList<T1@MutableList<T0@Int>>*/
|
||||
list/*T4@MutableList<T3@MutableList<T2@Int>>*/.get(0/*LIT*/)/*T3@MutableList<T2@Int>*/.add(1/*LIT*/)
|
||||
}
|
||||
|
||||
//T2 := T0 due to 'INITIALIZER'
|
||||
//T3 := T1 due to 'INITIALIZER'
|
||||
//T2 := T2 due to 'RECEIVER_PARAMETER'
|
||||
//T3 := T3 due to 'RECEIVER_PARAMETER'
|
||||
//T4 <: UPPER due to 'RECEIVER_PARAMETER'
|
||||
//T2 := T2 due to 'RECEIVER_PARAMETER'
|
||||
//T3 := LOWER due to 'USE_AS_RECEIVER'
|
||||
@@ -0,0 +1,9 @@
|
||||
fun a(
|
||||
l0: /*T2@*/MutableMap.MutableEntry</*T0@*/Int, /*T1@*/String>
|
||||
) {
|
||||
l0/*T2@MutableEntry<T0@Int, T1@String>*/.setValue(1/*LIT*/, "nya")
|
||||
}
|
||||
|
||||
//T0 := T0 due to 'RECEIVER_PARAMETER'
|
||||
//T1 := T1 due to 'RECEIVER_PARAMETER'
|
||||
//T2 := LOWER due to 'USE_AS_RECEIVER'
|
||||
@@ -0,0 +1,27 @@
|
||||
fun a(
|
||||
l0: /*T2@*/MutableMap</*T0@*/Int, /*T1@*/String>,
|
||||
l1: /*T5@*/MutableMap</*T3@*/Int, /*T4@*/String>,
|
||||
l2: /*T8@*/MutableMap</*T6@*/Int, /*T7@*/String>,
|
||||
l3: /*T11@*/MutableMap</*T9@*/Int, /*T10@*/String>
|
||||
) {
|
||||
l0/*T2@MutableMap<T0@Int, T1@String>*/.put(1)
|
||||
l1/*T5@MutableMap<T3@Int, T4@String>*/.remove(1/*LIT*/, l1)
|
||||
l2/*T8@MutableMap<T6@Int, T7@String>*/.putAll(l1/*T5@MutableMap<T3@Int, T4@String>*/)
|
||||
l3/*T11@MutableMap<T9@Int, T10@String>*/.clear(1)
|
||||
}
|
||||
|
||||
//T0 := T0 due to 'RECEIVER_PARAMETER'
|
||||
//T1 := T1 due to 'RECEIVER_PARAMETER'
|
||||
//T2 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T3 := T3 due to 'RECEIVER_PARAMETER'
|
||||
//T4 := T4 due to 'RECEIVER_PARAMETER'
|
||||
//T5 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T6 := T6 due to 'RECEIVER_PARAMETER'
|
||||
//T7 := T7 due to 'RECEIVER_PARAMETER'
|
||||
//T6 := T3 due to 'PARAMETER'
|
||||
//T7 := T4 due to 'PARAMETER'
|
||||
//T5 <: UPPER due to 'PARAMETER'
|
||||
//T8 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T9 := T9 due to 'RECEIVER_PARAMETER'
|
||||
//T10 := T10 due to 'RECEIVER_PARAMETER'
|
||||
//T11 := LOWER due to 'USE_AS_RECEIVER'
|
||||
@@ -0,0 +1,30 @@
|
||||
fun a(
|
||||
l0: /*T1@*/MutableSet</*T0@*/Int>,
|
||||
l1: /*T3@*/MutableSet</*T2@*/Int>,
|
||||
l2: /*T5@*/MutableSet</*T4@*/Int>,
|
||||
l3: /*T7@*/MutableSet</*T6@*/Int>,
|
||||
l4: /*T9@*/MutableSet</*T8@*/Int>
|
||||
) {
|
||||
l0/*T1@MutableSet<T0@Int>*/.add(1/*LIT*/)
|
||||
l1/*T3@MutableSet<T2@Int>*/.addAll(l1/*T3@MutableSet<T2@Int>*/)
|
||||
l2/*T5@MutableSet<T4@Int>*/.clear()
|
||||
l3/*T7@MutableSet<T6@Int>*/.removeAll(l1/*T3@MutableSet<T2@Int>*/)
|
||||
l4/*T9@MutableSet<T8@Int>*/.retainAll(l1/*T3@MutableSet<T2@Int>*/)
|
||||
}
|
||||
|
||||
//T0 := T0 due to 'RECEIVER_PARAMETER'
|
||||
//T1 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T2 := T2 due to 'RECEIVER_PARAMETER'
|
||||
//T2 := T2 due to 'PARAMETER'
|
||||
//T3 <: UPPER due to 'PARAMETER'
|
||||
//T3 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T4 := T4 due to 'RECEIVER_PARAMETER'
|
||||
//T5 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T6 := T6 due to 'RECEIVER_PARAMETER'
|
||||
//T6 := T2 due to 'PARAMETER'
|
||||
//T3 <: UPPER due to 'PARAMETER'
|
||||
//T7 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T8 := T8 due to 'RECEIVER_PARAMETER'
|
||||
//T8 := T2 due to 'PARAMETER'
|
||||
//T3 <: UPPER due to 'PARAMETER'
|
||||
//T9 := LOWER due to 'USE_AS_RECEIVER'
|
||||
+1
-1
@@ -4,5 +4,5 @@ fun test(a: /*T1@*/List</*T0@*/Int?>) {
|
||||
}
|
||||
}
|
||||
|
||||
//T2 <: T0 due to 'ASSIGNMENT'
|
||||
//T0 <: T2 due to 'ASSIGNMENT'
|
||||
//T1 := LOWER due to 'USE_AS_RECEIVER'
|
||||
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
fun bar(map: /*T2@*/HashMap</*T0@*/String?, /*T1@*/Int>, list1: /*T4@*/List</*T3@*/Int?>, list2: /*T6@*/List</*T5@*/String?>) {
|
||||
fun bar(map: /*T2@*/HashMap</*T0@*/String?, /*T1@*/Int>, list1: /*T4@*/List</*T3@*/Int>, list2: /*T6@*/List</*T5@*/String?>) {
|
||||
for (entry: /*T9@*/MutableMap.MutableEntry</*T7@*/String?, /*T8@*/Int> in map/*T2@HashMap<T0@String, T1@Int>*/.entries/*MutableSet<MutableEntry<T0@String, T1@Int>>*/) {
|
||||
val value: /*T10@*/Int = entry/*T9@MutableEntry<T7@String, T8@Int>*/.value/*T8@Int*/
|
||||
if (entry/*T9@MutableEntry<T7@String, T8@Int>*/.key/*T7@String*/ == null/*LIT*/) {
|
||||
@@ -21,11 +21,11 @@ fun bar(map: /*T2@*/HashMap</*T0@*/String?, /*T1@*/Int>, list1: /*T4@*/List</*T3
|
||||
//T9 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T7 := UPPER due to 'COMPARE_WITH_NULL'
|
||||
//T10 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T0 := T7 due to 'ASSIGNMENT'
|
||||
//T1 := T8 due to 'ASSIGNMENT'
|
||||
//T7 := T0 due to 'ASSIGNMENT'
|
||||
//T8 := T1 due to 'ASSIGNMENT'
|
||||
//T11 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T11 <: T3 due to 'ASSIGNMENT'
|
||||
//T3 <: T11 due to 'ASSIGNMENT'
|
||||
//T4 := LOWER due to 'USE_AS_RECEIVER'
|
||||
//T12 := UPPER due to 'COMPARE_WITH_NULL'
|
||||
//T12 <: T5 due to 'ASSIGNMENT'
|
||||
//T5 <: T12 due to 'ASSIGNMENT'
|
||||
//T6 := LOWER due to 'USE_AS_RECEIVER'
|
||||
|
||||
+9
-8
@@ -18,19 +18,19 @@ internal open class B {
|
||||
open val fromB1: String
|
||||
get() = ""
|
||||
|
||||
open var fromB2: String
|
||||
open var fromB2: String?
|
||||
get() = ""
|
||||
set(value) {}
|
||||
|
||||
open var fromB3: String
|
||||
open var fromB3: String?
|
||||
get() = ""
|
||||
set(value) {}
|
||||
|
||||
open var fromB4: String
|
||||
open var fromB4: String?
|
||||
get() = ""
|
||||
set(value) {}
|
||||
|
||||
open fun setFromB5(value: String) {}
|
||||
open fun setFromB5(value: String?) {}
|
||||
}
|
||||
|
||||
internal abstract class C(override val something1: Int) : B(), I {
|
||||
@@ -59,19 +59,19 @@ internal abstract class C(override val something1: Int) : B(), I {
|
||||
override val fromB1: String
|
||||
get() = super.fromB1
|
||||
|
||||
override var fromB2: String
|
||||
override var fromB2: String?
|
||||
get() = super.fromB2
|
||||
set(value) {
|
||||
super.fromB2 = value
|
||||
}
|
||||
|
||||
override var fromB3: String
|
||||
override var fromB3: String?
|
||||
get() = super.fromB3
|
||||
set(fromB3) {
|
||||
super.fromB3 = fromB3
|
||||
}
|
||||
|
||||
override var fromB4: String
|
||||
override var fromB4: String?
|
||||
get() = super.fromB4
|
||||
set(value) {
|
||||
super.fromB4 = value
|
||||
@@ -81,7 +81,8 @@ internal abstract class C(override val something1: Int) : B(), I {
|
||||
return ""
|
||||
}
|
||||
|
||||
override fun setFromB5(value: String) {
|
||||
override fun setFromB5(value: String?) {
|
||||
super.setFromB5(value)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
public class Nya {
|
||||
private int i;
|
||||
|
||||
public Nya(int i) {
|
||||
/*comment before*/
|
||||
this/*1*/./*2*/i/*3*/ = /*4*/i /* comment after*/; // and after again
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
class Nya /*comment before*/(/*1*//*3*/ /*4*/private val i: Int)
|
||||
@@ -1,5 +1,5 @@
|
||||
internal class A {
|
||||
var list: List<String?>? = null
|
||||
var list: List<String>? = null
|
||||
fun foo() {
|
||||
for (e in list!!) {
|
||||
println(e)
|
||||
|
||||
+1
-1
@@ -3,6 +3,6 @@ import java.util.ArrayList
|
||||
internal object A {
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
List::class.java.isAssignableFrom(ArrayList::class.java)
|
||||
MutableList::class.java.isAssignableFrom(ArrayList::class.java)
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
internal class X(private val list: MutableList<Y>) {
|
||||
internal class X(private val list: List<Y>) {
|
||||
|
||||
internal inner class Y
|
||||
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
// ERROR: Operator call corresponds to a dot-qualified call 'value.plus(1)' which is not allowed on a nullable receiver 'value'.
|
||||
import java.util.HashMap
|
||||
|
||||
internal enum class E { A, B, C }
|
||||
@@ -15,7 +14,7 @@ internal class A {
|
||||
val i = map.entries.iterator().next().key + 1
|
||||
}
|
||||
|
||||
fun bar(list: MutableList<String?>, map: HashMap<String?, Int?>) {
|
||||
fun bar(list: MutableList<String?>, map: HashMap<String, Int>) {
|
||||
val c = "a"[0]
|
||||
val b = 10.toByte()
|
||||
val i = 10.1.toInt()
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import java.util.Iterator;
|
||||
|
||||
public class TestMutableIterator implements Iterator<String> {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class Test {
|
||||
List<List<Integer>> list = new ArrayList<>();
|
||||
|
||||
public void test() {
|
||||
list.get(0).add(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import java.util.ArrayList
|
||||
|
||||
class Test {
|
||||
internal var list: List<List<Int>> = ArrayList()
|
||||
fun test() {
|
||||
list[0].add(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Owner {
|
||||
public List<String> list = new ArrayList<>();
|
||||
}
|
||||
|
||||
public class Updater {
|
||||
public void update(Owner owner) {
|
||||
owner.list.add("");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import java.util.ArrayList
|
||||
|
||||
class Owner {
|
||||
var list: MutableList<String> = ArrayList()
|
||||
}
|
||||
|
||||
class Updater {
|
||||
fun update(owner: Owner) {
|
||||
owner.list.add("")
|
||||
}
|
||||
}
|
||||
+15
@@ -1376,6 +1376,11 @@ public class NewJavaToKotlinConverterSingleFileTestGenerated extends AbstractNew
|
||||
runTest("nj2k/testData/newJ2k/detectProperties/CannotDropOnlySetter.java");
|
||||
}
|
||||
|
||||
@TestMetadata("commentInInitStatement.java")
|
||||
public void testCommentInInitStatement() throws Exception {
|
||||
runTest("nj2k/testData/newJ2k/detectProperties/commentInInitStatement.java");
|
||||
}
|
||||
|
||||
@TestMetadata("Comments.java")
|
||||
public void testComments() throws Exception {
|
||||
runTest("nj2k/testData/newJ2k/detectProperties/Comments.java");
|
||||
@@ -3628,6 +3633,16 @@ public class NewJavaToKotlinConverterSingleFileTestGenerated extends AbstractNew
|
||||
runTest("nj2k/testData/newJ2k/mutableCollections/Iterator2.java");
|
||||
}
|
||||
|
||||
@TestMetadata("listOfMutableList.java")
|
||||
public void testListOfMutableList() throws Exception {
|
||||
runTest("nj2k/testData/newJ2k/mutableCollections/listOfMutableList.java");
|
||||
}
|
||||
|
||||
@TestMetadata("mutableListInOtherClass.java")
|
||||
public void testMutableListInOtherClass() throws Exception {
|
||||
runTest("nj2k/testData/newJ2k/mutableCollections/mutableListInOtherClass.java");
|
||||
}
|
||||
|
||||
@TestMetadata("Overrides.java")
|
||||
public void testOverrides() throws Exception {
|
||||
runTest("nj2k/testData/newJ2k/mutableCollections/Overrides.java");
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ abstract class AbstractCommonConstraintCollectorTest : AbstractConstraintCollect
|
||||
override fun createInferenceFacade(resolutionFacade: ResolutionFacade): InferenceFacade =
|
||||
InferenceFacade(
|
||||
object : ContextCollector(resolutionFacade) {
|
||||
override fun ClassReference.getState(typeElement: KtTypeElement?): State? =
|
||||
override fun ClassReference.getState(typeElement: KtTypeElement?): State =
|
||||
State.UNKNOWN
|
||||
},
|
||||
ConstraintsCollectorAggregator(
|
||||
|
||||
Generated
+5
@@ -134,6 +134,11 @@ public class CommonConstraintCollectorTestGenerated extends AbstractCommonConstr
|
||||
runTest("nj2k/testData/inference/common/memberCall.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("newExpression.kt")
|
||||
public void testNewExpression() throws Exception {
|
||||
runTest("nj2k/testData/inference/common/newExpression.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("returnFromLambda.kt")
|
||||
public void testReturnFromLambda() throws Exception {
|
||||
runTest("nj2k/testData/inference/common/returnFromLambda.kt");
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.inference.mutability
|
||||
|
||||
import com.intellij.openapi.application.runWriteAction
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.formatter.commitAndUnblockDocument
|
||||
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
||||
import org.jetbrains.kotlin.nj2k.descriptorByFileDirective
|
||||
import org.jetbrains.kotlin.nj2k.inference.AbstractConstraintCollectorTest
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.*
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.collectors.CallExpressionConstraintCollector
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.collectors.CommonConstraintsCollector
|
||||
import org.jetbrains.kotlin.nj2k.inference.common.collectors.FunctionConstraintsCollector
|
||||
import org.jetbrains.kotlin.psi.KtConstructorCalleeExpression
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtTypeElement
|
||||
import org.jetbrains.kotlin.psi.KtTypeReference
|
||||
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractMutabilityInferenceTest : AbstractConstraintCollectorTest() {
|
||||
override fun createInferenceFacade(resolutionFacade: ResolutionFacade): InferenceFacade {
|
||||
val typeEnhancer = MutabilityBoundTypeEnhancer()
|
||||
return InferenceFacade(
|
||||
object : ContextCollector(resolutionFacade) {
|
||||
override fun ClassReference.getState(typeElement: KtTypeElement?) =
|
||||
when (descriptor?.fqNameOrNull()) {
|
||||
in MutabilityStateUpdater.mutableToImmutable -> State.UNKNOWN
|
||||
in MutabilityStateUpdater.immutableToMutable -> State.UNKNOWN
|
||||
else -> State.UNUSED
|
||||
}
|
||||
},
|
||||
ConstraintsCollectorAggregator(
|
||||
resolutionFacade,
|
||||
MutabilityConstraintBoundProvider(),
|
||||
listOf(
|
||||
CommonConstraintsCollector(),
|
||||
CallExpressionConstraintCollector(),
|
||||
FunctionConstraintsCollector(ResolveSuperFunctionsProvider(resolutionFacade)),
|
||||
MutabilityConstraintsCollector()
|
||||
)
|
||||
),
|
||||
MutabilityBoundTypeCalculator(resolutionFacade, typeEnhancer),
|
||||
MutabilityStateUpdater(),
|
||||
MutabilityDefaultStateProvider(),
|
||||
renderDebugTypes = true,
|
||||
printDebugConstraints = true
|
||||
)
|
||||
}
|
||||
|
||||
override fun KtFile.afterInference(): Unit = runWriteAction {
|
||||
commitAndUnblockDocument()
|
||||
ShortenReferences.DEFAULT.process(this)
|
||||
}
|
||||
|
||||
override fun KtFile.prepareFile() = runWriteAction {
|
||||
fun KtTypeReference.updateMutability() {
|
||||
MutabilityStateUpdater.changeState(
|
||||
typeElement ?: return,
|
||||
analyze()[BindingContext.TYPE, this]!!,
|
||||
toMutable = true
|
||||
)
|
||||
for (typeArgument in typeElement!!.typeArgumentsAsTypes) {
|
||||
typeArgument.updateMutability()
|
||||
}
|
||||
}
|
||||
for (typeReference in collectDescendantsOfType<KtTypeReference>()) {
|
||||
if (typeReference.parent is KtConstructorCalleeExpression) continue
|
||||
typeReference.updateMutability()
|
||||
}
|
||||
deleteComments()
|
||||
}
|
||||
|
||||
override fun getProjectDescriptor() =
|
||||
descriptorByFileDirective(File(testDataPath, fileName()), isAllFilesPresentInTest())
|
||||
}
|
||||
Generated
+96
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.inference.mutability;
|
||||
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils;
|
||||
import org.jetbrains.kotlin.test.TargetBackend;
|
||||
import org.jetbrains.kotlin.test.TestMetadata;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
|
||||
@SuppressWarnings("all")
|
||||
@TestMetadata("nj2k/testData/inference/mutability")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class MutabilityInferenceTestGenerated extends AbstractMutabilityInferenceTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInMutability() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("nj2k/testData/inference/mutability"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("arrayList.kt")
|
||||
public void testArrayList() throws Exception {
|
||||
runTest("nj2k/testData/inference/mutability/arrayList.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("collectionMutableCalls.kt")
|
||||
public void testCollectionMutableCalls() throws Exception {
|
||||
runTest("nj2k/testData/inference/mutability/collectionMutableCalls.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("covariance.kt")
|
||||
public void testCovariance() throws Exception {
|
||||
runTest("nj2k/testData/inference/mutability/covariance.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("iteratorCall.kt")
|
||||
public void testIteratorCall() throws Exception {
|
||||
runTest("nj2k/testData/inference/mutability/iteratorCall.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("IteratorMutableCalls.kt")
|
||||
public void testIteratorMutableCalls() throws Exception {
|
||||
runTest("nj2k/testData/inference/mutability/IteratorMutableCalls.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("list.kt")
|
||||
public void testList() throws Exception {
|
||||
runTest("nj2k/testData/inference/mutability/list.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("listIteratorMutableCalls.kt")
|
||||
public void testListIteratorMutableCalls() throws Exception {
|
||||
runTest("nj2k/testData/inference/mutability/listIteratorMutableCalls.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("listMutableCalls.kt")
|
||||
public void testListMutableCalls() throws Exception {
|
||||
runTest("nj2k/testData/inference/mutability/listMutableCalls.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("listOfListsForEach.kt")
|
||||
public void testListOfListsForEach() throws Exception {
|
||||
runTest("nj2k/testData/inference/mutability/listOfListsForEach.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("listOfMutableList.kt")
|
||||
public void testListOfMutableList() throws Exception {
|
||||
runTest("nj2k/testData/inference/mutability/listOfMutableList.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("mapEntryMutableCalls.kt")
|
||||
public void testMapEntryMutableCalls() throws Exception {
|
||||
runTest("nj2k/testData/inference/mutability/mapEntryMutableCalls.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("mapMutableCalls.kt")
|
||||
public void testMapMutableCalls() throws Exception {
|
||||
runTest("nj2k/testData/inference/mutability/mapMutableCalls.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("setMutableCalls.kt")
|
||||
public void testSetMutableCalls() throws Exception {
|
||||
runTest("nj2k/testData/inference/mutability/setMutableCalls.kt");
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -26,7 +26,7 @@ abstract class AbstractNullabilityInferenceTest : AbstractConstraintCollectorTes
|
||||
val typeEnhancer = NullabilityBoundTypeEnhancer(resolutionFacade)
|
||||
return InferenceFacade(
|
||||
object : ContextCollector(resolutionFacade) {
|
||||
override fun ClassReference.getState(typeElement: KtTypeElement?): State? =
|
||||
override fun ClassReference.getState(typeElement: KtTypeElement?): State =
|
||||
State.UNKNOWN
|
||||
},
|
||||
ConstraintsCollectorAggregator(
|
||||
|
||||
Reference in New Issue
Block a user