Extract variance check algorithm into 'core' module
This commit is contained in:
@@ -23,22 +23,15 @@ import org.jetbrains.kotlin.psi.KtCallableDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtTypeElement
|
||||
import org.jetbrains.kotlin.psi.KtTypeReference
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeProjection
|
||||
import org.jetbrains.kotlin.types.TypeProjectionImpl
|
||||
import org.jetbrains.kotlin.types.*
|
||||
|
||||
|
||||
interface TypeBinding<out P : PsiElement> {
|
||||
interface TypeBinding<out P : PsiElement> : TypeHolder<TypeBinding<P>> {
|
||||
val psiElement: P
|
||||
val kotlinType: KotlinType
|
||||
fun getArgumentBindings(): List<TypeArgumentBinding<P>?>
|
||||
override val arguments: List<TypeArgumentBinding<P>?>
|
||||
}
|
||||
|
||||
interface TypeArgumentBinding<out P: PsiElement> {
|
||||
val typeProjection: TypeProjection
|
||||
val typeParameterDescriptor: TypeParameterDescriptor?
|
||||
val typeBinding: TypeBinding<P>
|
||||
}
|
||||
interface TypeArgumentBinding<out P: PsiElement> : TypeHolderArgument<TypeBinding<P>>
|
||||
|
||||
fun KtTypeReference.createTypeBinding(trace: BindingContext): TypeBinding<KtTypeElement>? {
|
||||
val jetType = trace[BindingContext.TYPE, this]
|
||||
@@ -62,24 +55,25 @@ fun KtCallableDeclaration.createTypeBindingForReturnType(trace: BindingContext):
|
||||
}
|
||||
|
||||
private class TypeArgumentBindingImpl<out P: PsiElement>(
|
||||
override val typeProjection: TypeProjection,
|
||||
override val typeParameterDescriptor: TypeParameterDescriptor?,
|
||||
override val typeBinding: TypeBinding<P>
|
||||
override val projection: TypeProjection,
|
||||
override val typeParameter: TypeParameterDescriptor?,
|
||||
override val holder: TypeBinding<P>
|
||||
) : TypeArgumentBinding<P>
|
||||
|
||||
private class ExplicitTypeBinding(
|
||||
private val trace: BindingContext,
|
||||
override val psiElement: KtTypeElement,
|
||||
override val kotlinType: KotlinType
|
||||
override val type: KotlinType
|
||||
) : TypeBinding<KtTypeElement> {
|
||||
|
||||
override fun getArgumentBindings(): List<TypeArgumentBinding<KtTypeElement>?> {
|
||||
val psiTypeArguments = psiElement.typeArgumentsAsTypes
|
||||
val isErrorBinding = run {
|
||||
val sizeIsEqual = psiTypeArguments.size == kotlinType.arguments.size
|
||||
&& psiTypeArguments.size == kotlinType.constructor.parameters.size
|
||||
kotlinType.isError || !sizeIsEqual
|
||||
}
|
||||
override val arguments: List<TypeArgumentBinding<KtTypeElement>?>
|
||||
get() {
|
||||
val psiTypeArguments = psiElement.typeArgumentsAsTypes
|
||||
val isErrorBinding = run {
|
||||
val sizeIsEqual = psiTypeArguments.size == type.arguments.size
|
||||
&& psiTypeArguments.size == type.constructor.parameters.size
|
||||
type.isError || !sizeIsEqual
|
||||
}
|
||||
|
||||
return psiTypeArguments.indices.map { index: Int ->
|
||||
// todo fix for List<*>
|
||||
@@ -91,39 +85,40 @@ private class ExplicitTypeBinding(
|
||||
val nextJetType = trace[BindingContext.TYPE, jetTypeReference]
|
||||
if (nextJetType == null) return@map null
|
||||
|
||||
return@map TypeArgumentBindingImpl(
|
||||
TypeProjectionImpl(nextJetType),
|
||||
null,
|
||||
ExplicitTypeBinding(trace, jetTypeElement, nextJetType)
|
||||
)
|
||||
}
|
||||
|
||||
val typeProjection = type.arguments[index]
|
||||
return@map TypeArgumentBindingImpl(
|
||||
TypeProjectionImpl(nextJetType),
|
||||
null,
|
||||
ExplicitTypeBinding(trace, jetTypeElement, nextJetType)
|
||||
typeProjection,
|
||||
type.constructor.parameters[index],
|
||||
ExplicitTypeBinding(trace, jetTypeElement, typeProjection.type)
|
||||
)
|
||||
}
|
||||
|
||||
val typeProjection = kotlinType.arguments[index]
|
||||
return@map TypeArgumentBindingImpl(
|
||||
typeProjection,
|
||||
kotlinType.constructor.parameters[index],
|
||||
ExplicitTypeBinding(trace, jetTypeElement, typeProjection.type)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class NoTypeElementBinding<out P : PsiElement>(
|
||||
private val trace: BindingContext,
|
||||
override val psiElement: P,
|
||||
override val kotlinType: KotlinType
|
||||
override val type: KotlinType
|
||||
): TypeBinding<P> {
|
||||
|
||||
override fun getArgumentBindings(): List<TypeArgumentBinding<P>?> {
|
||||
val isErrorBinding = kotlinType.isError || kotlinType.constructor.parameters.size != kotlinType.arguments.size
|
||||
return kotlinType.arguments.indices.map {
|
||||
val typeProjection = kotlinType.arguments[it]
|
||||
TypeArgumentBindingImpl(
|
||||
typeProjection,
|
||||
if (isErrorBinding) null else kotlinType.constructor.parameters[it],
|
||||
NoTypeElementBinding(trace, psiElement, typeProjection.type)
|
||||
)
|
||||
override val arguments: List<TypeArgumentBinding<P>?>
|
||||
get() {
|
||||
val isErrorBinding = type.isError || type.constructor.parameters.size != type.arguments.size
|
||||
return type.arguments.indices.map {
|
||||
val typeProjection = type.arguments[it]
|
||||
TypeArgumentBindingImpl(
|
||||
typeProjection,
|
||||
if (isErrorBinding) null else type.constructor.parameters[it],
|
||||
NoTypeElementBinding(trace, psiElement, typeProjection.type)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.descriptors.impl.PropertyAccessorDescriptorImpl
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.types.*
|
||||
|
||||
class ManualVariance(val descriptor: TypeParameterDescriptor, val variance: Variance)
|
||||
|
||||
@@ -160,41 +161,21 @@ class VarianceCheckerCore(
|
||||
private fun KtTypeReference.checkTypePosition(trace: BindingContext, position: Variance)
|
||||
= createTypeBinding(trace)?.checkTypePosition(position)
|
||||
|
||||
private fun TypeBinding<PsiElement>.checkTypePosition(position: Variance) = checkTypePosition(kotlinType, position)
|
||||
private fun TypeBinding<PsiElement>.checkTypePosition(position: Variance) = checkTypePosition(type, position)
|
||||
|
||||
private fun TypeBinding<PsiElement>.checkTypePosition(containingType: KotlinType, position: Variance): Boolean {
|
||||
val classifierDescriptor = kotlinType.constructor.declarationDescriptor
|
||||
if (classifierDescriptor is TypeParameterDescriptor) {
|
||||
val declarationVariance = classifierDescriptor.varianceWithManual()
|
||||
if (!declarationVariance.allowsPosition(position)
|
||||
&& !kotlinType.annotations.hasAnnotation(KotlinBuiltIns.FQ_NAMES.unsafeVariance)) {
|
||||
diagnosticSink.report(
|
||||
Errors.TYPE_VARIANCE_CONFLICT.on(
|
||||
psiElement,
|
||||
VarianceConflictDiagnosticData(containingType, classifierDescriptor, position)
|
||||
)
|
||||
)
|
||||
}
|
||||
return declarationVariance.allowsPosition(position)
|
||||
}
|
||||
|
||||
var noError = true
|
||||
for (argumentBinding in getArgumentBindings()) {
|
||||
if (argumentBinding == null || argumentBinding.typeParameterDescriptor == null) continue
|
||||
|
||||
val projectionKind = getEffectiveProjectionKind(argumentBinding.typeParameterDescriptor!!, argumentBinding.typeProjection)!!
|
||||
val newPosition = when (projectionKind) {
|
||||
OUT -> position
|
||||
IN -> position.opposite()
|
||||
INV -> INVARIANT
|
||||
STAR -> null // CONFLICTING_PROJECTION error was reported
|
||||
}
|
||||
if (newPosition != null) {
|
||||
noError = noError and argumentBinding.typeBinding.checkTypePosition(containingType, newPosition)
|
||||
}
|
||||
}
|
||||
return noError
|
||||
}
|
||||
private fun TypeBinding<PsiElement>.checkTypePosition(containingType: KotlinType, position: Variance): Boolean =
|
||||
checkTypePosition(
|
||||
position,
|
||||
{ typeParameterDescriptor, typeBinding, errorPosition ->
|
||||
diagnosticSink.report(
|
||||
Errors.TYPE_VARIANCE_CONFLICT.on(
|
||||
typeBinding.psiElement,
|
||||
VarianceConflictDiagnosticData(containingType, typeParameterDescriptor, errorPosition)
|
||||
)
|
||||
)
|
||||
},
|
||||
customVariance = { it.varianceWithManual() }
|
||||
)
|
||||
|
||||
private fun isIrrelevant(descriptor: CallableDescriptor): Boolean {
|
||||
val containingClass = descriptor.containingDeclaration as? ClassDescriptor ?: return true
|
||||
|
||||
@@ -75,9 +75,9 @@ abstract class AbstractTypeBindingTest : KotlinTestWithEnvironment() {
|
||||
println("null")
|
||||
return this
|
||||
}
|
||||
println("typeParameter: ${argument.typeParameterDescriptor.render()}")
|
||||
println("typeParameter: ${argument.typeParameter.render()}")
|
||||
|
||||
val projection = argument.typeProjection.projectionKind.label.let {
|
||||
val projection = argument.projection.projectionKind.label.let {
|
||||
if (it.isNotEmpty())
|
||||
"$it "
|
||||
else
|
||||
@@ -85,10 +85,10 @@ abstract class AbstractTypeBindingTest : KotlinTestWithEnvironment() {
|
||||
}
|
||||
|
||||
print("typeProjection: ")
|
||||
if (argument.typeProjection.isStarProjection)
|
||||
if (argument.projection.isStarProjection)
|
||||
printlnWithNoIndent("*")
|
||||
else printlnWithNoIndent("${projection}${argument.typeProjection.type.render()}")
|
||||
print(argument.typeBinding)
|
||||
else printlnWithNoIndent("${projection}${argument.projection.type.render()}")
|
||||
print(argument.holder)
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -99,9 +99,9 @@ abstract class AbstractTypeBindingTest : KotlinTestWithEnvironment() {
|
||||
}
|
||||
|
||||
println("psi: ${binding.psiElement.text}")
|
||||
println("type: ${binding.kotlinType.render()}")
|
||||
println("type: ${binding.type.render()}")
|
||||
|
||||
printCollection(binding.getArgumentBindings()) {
|
||||
printCollection(binding.arguments) {
|
||||
print(it)
|
||||
}
|
||||
return this
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.types
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
|
||||
import org.jetbrains.kotlin.types.checker.TypeCheckingProcedure
|
||||
import org.jetbrains.kotlin.types.checker.TypeCheckingProcedure.EnrichedProjectionKind
|
||||
import org.jetbrains.kotlin.utils.DO_NOTHING_3
|
||||
|
||||
interface TypeHolder<out D : TypeHolder<D>> {
|
||||
val type: KotlinType
|
||||
val arguments: List<TypeHolderArgument<D>?>
|
||||
}
|
||||
|
||||
interface TypeHolderArgument<out D : TypeHolder<D>> {
|
||||
val projection: TypeProjection
|
||||
val typeParameter: TypeParameterDescriptor?
|
||||
val holder: D
|
||||
}
|
||||
|
||||
fun <D : TypeHolder<D>> D.checkTypePosition(
|
||||
position: Variance,
|
||||
reportError: (TypeParameterDescriptor, D, Variance) -> Unit = DO_NOTHING_3,
|
||||
customVariance: (TypeParameterDescriptor) -> Variance? = { null }
|
||||
): Boolean {
|
||||
val classifierDescriptor = type.constructor.declarationDescriptor
|
||||
if (classifierDescriptor is TypeParameterDescriptor) {
|
||||
val declarationVariance = customVariance(classifierDescriptor) ?: classifierDescriptor.variance
|
||||
if (!declarationVariance.allowsPosition(position)
|
||||
&& !type.annotations.hasAnnotation(org.jetbrains.kotlin.builtins.KotlinBuiltIns.FQ_NAMES.unsafeVariance)) {
|
||||
reportError(classifierDescriptor, this, position)
|
||||
}
|
||||
return declarationVariance.allowsPosition(position)
|
||||
}
|
||||
|
||||
var noError = true
|
||||
for (argument in arguments) {
|
||||
if (argument == null || argument.typeParameter == null) continue
|
||||
|
||||
val projectionKind = TypeCheckingProcedure.getEffectiveProjectionKind(argument.typeParameter!!, argument.projection)!!
|
||||
val newPosition = when (projectionKind) {
|
||||
EnrichedProjectionKind.OUT -> position
|
||||
EnrichedProjectionKind.IN -> position.opposite()
|
||||
EnrichedProjectionKind.INV -> Variance.INVARIANT
|
||||
EnrichedProjectionKind.STAR -> null // CONFLICTING_PROJECTION error was reported
|
||||
}
|
||||
if (newPosition != null) {
|
||||
noError = noError and argument.holder.checkTypePosition(newPosition, reportError, customVariance)
|
||||
}
|
||||
}
|
||||
return noError
|
||||
}
|
||||
Reference in New Issue
Block a user