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.KtTypeElement
|
||||||
import org.jetbrains.kotlin.psi.KtTypeReference
|
import org.jetbrains.kotlin.psi.KtTypeReference
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.*
|
||||||
import org.jetbrains.kotlin.types.TypeProjection
|
|
||||||
import org.jetbrains.kotlin.types.TypeProjectionImpl
|
|
||||||
|
|
||||||
|
|
||||||
interface TypeBinding<out P : PsiElement> {
|
interface TypeBinding<out P : PsiElement> : TypeHolder<TypeBinding<P>> {
|
||||||
val psiElement: P
|
val psiElement: P
|
||||||
val kotlinType: KotlinType
|
override val arguments: List<TypeArgumentBinding<P>?>
|
||||||
fun getArgumentBindings(): List<TypeArgumentBinding<P>?>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TypeArgumentBinding<out P: PsiElement> {
|
interface TypeArgumentBinding<out P: PsiElement> : TypeHolderArgument<TypeBinding<P>>
|
||||||
val typeProjection: TypeProjection
|
|
||||||
val typeParameterDescriptor: TypeParameterDescriptor?
|
|
||||||
val typeBinding: TypeBinding<P>
|
|
||||||
}
|
|
||||||
|
|
||||||
fun KtTypeReference.createTypeBinding(trace: BindingContext): TypeBinding<KtTypeElement>? {
|
fun KtTypeReference.createTypeBinding(trace: BindingContext): TypeBinding<KtTypeElement>? {
|
||||||
val jetType = trace[BindingContext.TYPE, this]
|
val jetType = trace[BindingContext.TYPE, this]
|
||||||
@@ -62,24 +55,25 @@ fun KtCallableDeclaration.createTypeBindingForReturnType(trace: BindingContext):
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class TypeArgumentBindingImpl<out P: PsiElement>(
|
private class TypeArgumentBindingImpl<out P: PsiElement>(
|
||||||
override val typeProjection: TypeProjection,
|
override val projection: TypeProjection,
|
||||||
override val typeParameterDescriptor: TypeParameterDescriptor?,
|
override val typeParameter: TypeParameterDescriptor?,
|
||||||
override val typeBinding: TypeBinding<P>
|
override val holder: TypeBinding<P>
|
||||||
) : TypeArgumentBinding<P>
|
) : TypeArgumentBinding<P>
|
||||||
|
|
||||||
private class ExplicitTypeBinding(
|
private class ExplicitTypeBinding(
|
||||||
private val trace: BindingContext,
|
private val trace: BindingContext,
|
||||||
override val psiElement: KtTypeElement,
|
override val psiElement: KtTypeElement,
|
||||||
override val kotlinType: KotlinType
|
override val type: KotlinType
|
||||||
) : TypeBinding<KtTypeElement> {
|
) : TypeBinding<KtTypeElement> {
|
||||||
|
|
||||||
override fun getArgumentBindings(): List<TypeArgumentBinding<KtTypeElement>?> {
|
override val arguments: List<TypeArgumentBinding<KtTypeElement>?>
|
||||||
val psiTypeArguments = psiElement.typeArgumentsAsTypes
|
get() {
|
||||||
val isErrorBinding = run {
|
val psiTypeArguments = psiElement.typeArgumentsAsTypes
|
||||||
val sizeIsEqual = psiTypeArguments.size == kotlinType.arguments.size
|
val isErrorBinding = run {
|
||||||
&& psiTypeArguments.size == kotlinType.constructor.parameters.size
|
val sizeIsEqual = psiTypeArguments.size == type.arguments.size
|
||||||
kotlinType.isError || !sizeIsEqual
|
&& psiTypeArguments.size == type.constructor.parameters.size
|
||||||
}
|
type.isError || !sizeIsEqual
|
||||||
|
}
|
||||||
|
|
||||||
return psiTypeArguments.indices.map { index: Int ->
|
return psiTypeArguments.indices.map { index: Int ->
|
||||||
// todo fix for List<*>
|
// todo fix for List<*>
|
||||||
@@ -91,39 +85,40 @@ private class ExplicitTypeBinding(
|
|||||||
val nextJetType = trace[BindingContext.TYPE, jetTypeReference]
|
val nextJetType = trace[BindingContext.TYPE, jetTypeReference]
|
||||||
if (nextJetType == null) return@map null
|
if (nextJetType == null) return@map null
|
||||||
|
|
||||||
|
return@map TypeArgumentBindingImpl(
|
||||||
|
TypeProjectionImpl(nextJetType),
|
||||||
|
null,
|
||||||
|
ExplicitTypeBinding(trace, jetTypeElement, nextJetType)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val typeProjection = type.arguments[index]
|
||||||
return@map TypeArgumentBindingImpl(
|
return@map TypeArgumentBindingImpl(
|
||||||
TypeProjectionImpl(nextJetType),
|
typeProjection,
|
||||||
null,
|
type.constructor.parameters[index],
|
||||||
ExplicitTypeBinding(trace, jetTypeElement, nextJetType)
|
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 class NoTypeElementBinding<out P : PsiElement>(
|
||||||
private val trace: BindingContext,
|
private val trace: BindingContext,
|
||||||
override val psiElement: P,
|
override val psiElement: P,
|
||||||
override val kotlinType: KotlinType
|
override val type: KotlinType
|
||||||
): TypeBinding<P> {
|
): TypeBinding<P> {
|
||||||
|
|
||||||
override fun getArgumentBindings(): List<TypeArgumentBinding<P>?> {
|
override val arguments: List<TypeArgumentBinding<P>?>
|
||||||
val isErrorBinding = kotlinType.isError || kotlinType.constructor.parameters.size != kotlinType.arguments.size
|
get() {
|
||||||
return kotlinType.arguments.indices.map {
|
val isErrorBinding = type.isError || type.constructor.parameters.size != type.arguments.size
|
||||||
val typeProjection = kotlinType.arguments[it]
|
return type.arguments.indices.map {
|
||||||
TypeArgumentBindingImpl(
|
val typeProjection = type.arguments[it]
|
||||||
typeProjection,
|
TypeArgumentBindingImpl(
|
||||||
if (isErrorBinding) null else kotlinType.constructor.parameters[it],
|
typeProjection,
|
||||||
NoTypeElementBinding(trace, psiElement, typeProjection.type)
|
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.resolve.source.getPsi
|
||||||
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.types.*
|
||||||
|
|
||||||
class ManualVariance(val descriptor: TypeParameterDescriptor, val variance: Variance)
|
class ManualVariance(val descriptor: TypeParameterDescriptor, val variance: Variance)
|
||||||
|
|
||||||
@@ -160,41 +161,21 @@ class VarianceCheckerCore(
|
|||||||
private fun KtTypeReference.checkTypePosition(trace: BindingContext, position: Variance)
|
private fun KtTypeReference.checkTypePosition(trace: BindingContext, position: Variance)
|
||||||
= createTypeBinding(trace)?.checkTypePosition(position)
|
= 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 {
|
private fun TypeBinding<PsiElement>.checkTypePosition(containingType: KotlinType, position: Variance): Boolean =
|
||||||
val classifierDescriptor = kotlinType.constructor.declarationDescriptor
|
checkTypePosition(
|
||||||
if (classifierDescriptor is TypeParameterDescriptor) {
|
position,
|
||||||
val declarationVariance = classifierDescriptor.varianceWithManual()
|
{ typeParameterDescriptor, typeBinding, errorPosition ->
|
||||||
if (!declarationVariance.allowsPosition(position)
|
diagnosticSink.report(
|
||||||
&& !kotlinType.annotations.hasAnnotation(KotlinBuiltIns.FQ_NAMES.unsafeVariance)) {
|
Errors.TYPE_VARIANCE_CONFLICT.on(
|
||||||
diagnosticSink.report(
|
typeBinding.psiElement,
|
||||||
Errors.TYPE_VARIANCE_CONFLICT.on(
|
VarianceConflictDiagnosticData(containingType, typeParameterDescriptor, errorPosition)
|
||||||
psiElement,
|
)
|
||||||
VarianceConflictDiagnosticData(containingType, classifierDescriptor, position)
|
)
|
||||||
)
|
},
|
||||||
)
|
customVariance = { it.varianceWithManual() }
|
||||||
}
|
)
|
||||||
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 isIrrelevant(descriptor: CallableDescriptor): Boolean {
|
private fun isIrrelevant(descriptor: CallableDescriptor): Boolean {
|
||||||
val containingClass = descriptor.containingDeclaration as? ClassDescriptor ?: return true
|
val containingClass = descriptor.containingDeclaration as? ClassDescriptor ?: return true
|
||||||
|
|||||||
@@ -75,9 +75,9 @@ abstract class AbstractTypeBindingTest : KotlinTestWithEnvironment() {
|
|||||||
println("null")
|
println("null")
|
||||||
return this
|
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())
|
if (it.isNotEmpty())
|
||||||
"$it "
|
"$it "
|
||||||
else
|
else
|
||||||
@@ -85,10 +85,10 @@ abstract class AbstractTypeBindingTest : KotlinTestWithEnvironment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
print("typeProjection: ")
|
print("typeProjection: ")
|
||||||
if (argument.typeProjection.isStarProjection)
|
if (argument.projection.isStarProjection)
|
||||||
printlnWithNoIndent("*")
|
printlnWithNoIndent("*")
|
||||||
else printlnWithNoIndent("${projection}${argument.typeProjection.type.render()}")
|
else printlnWithNoIndent("${projection}${argument.projection.type.render()}")
|
||||||
print(argument.typeBinding)
|
print(argument.holder)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,9 +99,9 @@ abstract class AbstractTypeBindingTest : KotlinTestWithEnvironment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
println("psi: ${binding.psiElement.text}")
|
println("psi: ${binding.psiElement.text}")
|
||||||
println("type: ${binding.kotlinType.render()}")
|
println("type: ${binding.type.render()}")
|
||||||
|
|
||||||
printCollection(binding.getArgumentBindings()) {
|
printCollection(binding.arguments) {
|
||||||
print(it)
|
print(it)
|
||||||
}
|
}
|
||||||
return this
|
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