Extract variance check algorithm into 'core' module

This commit is contained in:
Denis Zharkov
2016-05-12 16:25:34 +03:00
parent c95e11cc7e
commit 279ff0b561
4 changed files with 127 additions and 85 deletions
@@ -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
}