From 279ff0b5618376e564cb4831769cbd532a61d24a Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Thu, 12 May 2016 16:25:34 +0300 Subject: [PATCH] Extract variance check algorithm into 'core' module --- .../jetbrains/kotlin/resolve/TypeBinding.kt | 83 +++++++++---------- .../kotlin/resolve/VarianceChecker.kt | 49 ++++------- .../kotlin/types/AbstractTypeBindingTest.kt | 14 ++-- .../jetbrains/kotlin/types/VarianceChecker.kt | 66 +++++++++++++++ 4 files changed, 127 insertions(+), 85 deletions(-) create mode 100644 core/descriptors/src/org/jetbrains/kotlin/types/VarianceChecker.kt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/TypeBinding.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/TypeBinding.kt index 14935a760f6..f0f0161bac3 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/TypeBinding.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/TypeBinding.kt @@ -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 { +interface TypeBinding : TypeHolder> { val psiElement: P - val kotlinType: KotlinType - fun getArgumentBindings(): List?> + override val arguments: List?> } -interface TypeArgumentBinding { - val typeProjection: TypeProjection - val typeParameterDescriptor: TypeParameterDescriptor? - val typeBinding: TypeBinding

-} +interface TypeArgumentBinding : TypeHolderArgument> fun KtTypeReference.createTypeBinding(trace: BindingContext): TypeBinding? { val jetType = trace[BindingContext.TYPE, this] @@ -62,24 +55,25 @@ fun KtCallableDeclaration.createTypeBindingForReturnType(trace: BindingContext): } private class TypeArgumentBindingImpl( - override val typeProjection: TypeProjection, - override val typeParameterDescriptor: TypeParameterDescriptor?, - override val typeBinding: TypeBinding

+ override val projection: TypeProjection, + override val typeParameter: TypeParameterDescriptor?, + override val holder: TypeBinding

) : TypeArgumentBinding

private class ExplicitTypeBinding( private val trace: BindingContext, override val psiElement: KtTypeElement, - override val kotlinType: KotlinType + override val type: KotlinType ) : TypeBinding { - override fun getArgumentBindings(): List?> { - 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?> + 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( private val trace: BindingContext, override val psiElement: P, - override val kotlinType: KotlinType + override val type: KotlinType ): TypeBinding

{ - override fun getArgumentBindings(): List?> { - 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?> + 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) + ) + } } - } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/VarianceChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/VarianceChecker.kt index aa3e4384379..31fd5bc68cb 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/VarianceChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/VarianceChecker.kt @@ -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.checkTypePosition(position: Variance) = checkTypePosition(kotlinType, position) + private fun TypeBinding.checkTypePosition(position: Variance) = checkTypePosition(type, position) - private fun TypeBinding.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.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 diff --git a/compiler/tests/org/jetbrains/kotlin/types/AbstractTypeBindingTest.kt b/compiler/tests/org/jetbrains/kotlin/types/AbstractTypeBindingTest.kt index b6084bc868b..76396173bc7 100644 --- a/compiler/tests/org/jetbrains/kotlin/types/AbstractTypeBindingTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/types/AbstractTypeBindingTest.kt @@ -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 diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/VarianceChecker.kt b/core/descriptors/src/org/jetbrains/kotlin/types/VarianceChecker.kt new file mode 100644 index 00000000000..1502842d8d9 --- /dev/null +++ b/core/descriptors/src/org/jetbrains/kotlin/types/VarianceChecker.kt @@ -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> { + val type: KotlinType + val arguments: List?> +} + +interface TypeHolderArgument> { + val projection: TypeProjection + val typeParameter: TypeParameterDescriptor? + val holder: D +} + +fun > 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 +}