From c2d99a40cd4056c9a7795781898d8381a691367b Mon Sep 17 00:00:00 2001 From: Stanislav Erokhin Date: Tue, 18 Nov 2014 12:59:42 +0300 Subject: [PATCH] Create VarianceChecker --- .../jet/lang/diagnostics/Errors.java | 3 + .../rendering/DefaultErrorMessages.java | 16 ++ .../lang/diagnostics/rendering/Renderers.java | 13 ++ .../jet/lang/resolve/OverrideResolver.java | 2 +- .../lang/resolve/TopDownAnalysisContext.java | 5 +- .../jet/lang/resolve/VarianceChecker.kt | 196 ++++++++++++++++++ .../jet/lang/descriptors/Visibilities.java | 6 + 7 files changed, 238 insertions(+), 3 deletions(-) create mode 100644 compiler/frontend/src/org/jetbrains/jet/lang/resolve/VarianceChecker.kt diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java index f2fa84c13c3..64da21ddb7b 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java @@ -27,6 +27,7 @@ import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.calls.inference.InferenceErrorData; import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; import org.jetbrains.jet.lang.resolve.name.Name; +import org.jetbrains.jet.lang.resolve.varianceChecker.VarianceChecker.VarianceConflictDiagnosticData; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lexer.JetKeywordToken; import org.jetbrains.jet.lexer.JetModifierKeywordToken; @@ -88,6 +89,8 @@ public interface Errors { DiagnosticFactory2 NO_TYPE_ARGUMENTS_ON_RHS = DiagnosticFactory2.create(ERROR); DiagnosticFactory1 CONFLICTING_PROJECTION = DiagnosticFactory1.create(ERROR, VARIANCE_IN_PROJECTION); DiagnosticFactory1 REDUNDANT_PROJECTION = DiagnosticFactory1.create(WARNING, VARIANCE_IN_PROJECTION); + DiagnosticFactory1 TYPE_VARIANCE_CONFLICT = + DiagnosticFactory1.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/DefaultErrorMessages.java index 39b1fd562c5..cad14835669 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/DefaultErrorMessages.java @@ -28,9 +28,11 @@ import org.jetbrains.jet.lang.diagnostics.Errors; import org.jetbrains.jet.lang.psi.JetExpression; import org.jetbrains.jet.lang.psi.JetSimpleNameExpression; import org.jetbrains.jet.lang.psi.JetTypeConstraint; +import org.jetbrains.jet.lang.resolve.varianceChecker.VarianceChecker.VarianceConflictDiagnosticData; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lexer.JetKeywordToken; import org.jetbrains.jet.lexer.JetModifierKeywordToken; +import org.jetbrains.jet.renderer.MultiRenderer; import org.jetbrains.jet.renderer.Renderer; import java.lang.reflect.Field; @@ -388,6 +390,20 @@ public class DefaultErrorMessages { "Smart cast to ''{0}'' is impossible, because ''{1}'' could have changed since the is-check", RENDER_TYPE, STRING); MAP.put(VARIANCE_ON_TYPE_PARAMETER_OF_FUNCTION_OR_PROPERTY, "Variance annotations are only allowed for type parameters of classes and traits"); + MAP.put(TYPE_VARIANCE_CONFLICT, "Type parameter {0} is declared as ''{1}'' but occurs in ''{2}'' position in type {3}", + new MultiRenderer() { + @NotNull + @Override + public String[] render(@NotNull VarianceConflictDiagnosticData data) { + return new String[] { + NAME.render(data.getTypeParameter()), + RENDER_POSITION_VARIANCE.render(data.getTypeParameter().getVariance()), + RENDER_POSITION_VARIANCE.render(data.getOccurrencePosition()), + RENDER_TYPE.render(data.getContainingType()) + }; + } + }); + MAP.put(REDUNDANT_PROJECTION, "Projection is redundant: the corresponding type parameter of {0} has the same variance", NAME); MAP.put(CONFLICTING_PROJECTION, "Projection is conflicting with variance of the corresponding type parameter of {0}. Remove the projection or replace it with ''*''", NAME); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/Renderers.java b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/Renderers.java index 1baf06619b6..2a59a1083dd 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/Renderers.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/Renderers.java @@ -131,6 +131,19 @@ public class Renderers { } }; + public static final Renderer RENDER_POSITION_VARIANCE = new Renderer() { + @NotNull + @Override + public String render(@NotNull Variance variance) { + switch (variance) { + case INVARIANT: return "invariant"; + case IN_VARIANCE: return "in"; + case OUT_VARIANCE: return "out"; + } + throw new IllegalArgumentException("Unknown variance: " + variance); + } + }; + public static final Renderer>> AMBIGUOUS_CALLS = new Renderer>>() { @NotNull diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/OverrideResolver.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/OverrideResolver.java index 396e0c01dbb..768e01cdde2 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/OverrideResolver.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/OverrideResolver.java @@ -939,7 +939,7 @@ public class OverrideResolver { } private void checkVisibility(@NotNull TopDownAnalysisContext c) { - for (Map.Entry entry : c.getMembers().entrySet()) { + for (Map.Entry entry : c.getMembers().entrySet()) { checkVisibilityForMember(entry.getKey(), entry.getValue()); } } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/TopDownAnalysisContext.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/TopDownAnalysisContext.java index ecdd5a60742..7680ceef114 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/TopDownAnalysisContext.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/TopDownAnalysisContext.java @@ -51,7 +51,7 @@ public class TopDownAnalysisContext implements BodiesResolveContext { private final Map functions = Maps.newLinkedHashMap(); private final Map properties = Maps.newLinkedHashMap(); private final Map primaryConstructorParameterProperties = Maps.newHashMap(); - private Map members = null; + private Map members = null; // File scopes - package scope extended with imports protected final Map fileScopes = Maps.newHashMap(); @@ -173,7 +173,8 @@ public class TopDownAnalysisContext implements BodiesResolveContext { return functions; } - public Map getMembers() { + @NotNull + public Map getMembers() { if (members == null) { members = Maps.newHashMap(); members.putAll(functions); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/VarianceChecker.kt b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/VarianceChecker.kt new file mode 100644 index 00000000000..dd0aa8a26a0 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/VarianceChecker.kt @@ -0,0 +1,196 @@ +/* + * Copyright 2010-2014 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.jet.lang.resolve.varianceChecker + +import org.jetbrains.jet.lang.descriptors.ClassDescriptor +import org.jetbrains.jet.lang.diagnostics.Errors +import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor +import org.jetbrains.jet.lang.types.Variance +import org.jetbrains.jet.lang.types.checker.TypeCheckingProcedure.EnrichedProjectionKind.* +import org.jetbrains.jet.lang.types.checker.TypeCheckingProcedure.* +import org.jetbrains.jet.lang.descriptors.Visibilities +import org.jetbrains.jet.lang.resolve.BindingTrace +import org.jetbrains.jet.lang.resolve.typeBinding.TypeBinding +import com.intellij.psi.PsiElement +import org.jetbrains.jet.lang.resolve.typeBinding.createTypeBinding +import org.jetbrains.jet.lang.resolve.typeBinding.createTypeBindingForReturnType +import org.jetbrains.jet.lang.psi.JetCallableDeclaration +import org.jetbrains.jet.lang.descriptors.CallableDescriptor +import org.jetbrains.jet.lang.resolve.TopDownAnalysisContext +import org.jetbrains.jet.lang.types.JetType +import org.jetbrains.jet.lang.psi.JetClass +import org.jetbrains.jet.lang.psi.JetTypeReference +import org.jetbrains.jet.lang.types.Variance.* +import org.jetbrains.jet.lang.diagnostics.DiagnosticSink +import org.jetbrains.jet.lang.descriptors.CallableMemberDescriptor +import org.jetbrains.jet.lang.descriptors.impl.FunctionDescriptorImpl +import org.jetbrains.jet.lang.descriptors.impl.PropertyDescriptorImpl +import org.jetbrains.jet.lang.descriptors.impl.PropertyAccessorDescriptorImpl +import org.jetbrains.jet.lang.resolve.source.getPsi +import org.jetbrains.jet.lang.descriptors.VariableDescriptor +import org.jetbrains.jet.lang.psi.JetTypeParameterListOwner +import org.jetbrains.jet.lang.resolve.BindingContext +import kotlin.platform.platformStatic + + +class VarianceChecker(private val trace: BindingTrace) { + + fun process(c: TopDownAnalysisContext) { + for (member in c.getMembers().values()) { + recordPrivateToThisIfNeeded(trace, member) + } + + check(c) + } + + fun check(c: TopDownAnalysisContext) { + checkClasses(c) + checkMembers(c) + } + + private fun checkClasses(c: TopDownAnalysisContext) { + for (jetClassOrObject in c.getDeclaredClasses()!!.keySet()) { + if (jetClassOrObject is JetClass) { + for (specifier in jetClassOrObject.getDelegationSpecifiers()) { + specifier.getTypeReference()?.checkTypePosition(trace.getBindingContext(), OUT_VARIANCE, trace) + } + jetClassOrObject.checkTypeParameters(trace.getBindingContext(), OUT_VARIANCE, trace) + } + } + } + + private fun checkMembers(c: TopDownAnalysisContext) { + for ((declaration, descriptor) in c.getMembers()) { + if (!Visibilities.isPrivate(descriptor.getVisibility())) { + checkCallableDeclaration(trace.getBindingContext(), declaration, descriptor, trace) + } + } + } + + class VarianceConflictDiagnosticData( + val containingType: JetType, + val typeParameter: TypeParameterDescriptor, + val occurrencePosition: Variance + ) + + class object { + platformStatic fun recordPrivateToThisIfNeeded(trace: BindingTrace, descriptor: CallableMemberDescriptor) { + if (descriptor.getVisibility() != Visibilities.PRIVATE) return + + val psiElement = descriptor.getSource().getPsi() + if (psiElement !is JetCallableDeclaration) return + + if (!checkCallableDeclaration(trace.getBindingContext(), psiElement, descriptor, DiagnosticSink.DO_NOTHING)) { + recordPrivateToThis(descriptor) + } + } + + private fun recordPrivateToThis(descriptor: CallableDescriptor) { + if (descriptor is FunctionDescriptorImpl) { + descriptor.setVisibility(Visibilities.PRIVATE_TO_THIS); + } + else if (descriptor is PropertyDescriptorImpl) { + descriptor.setVisibility(Visibilities.PRIVATE_TO_THIS); + for (accessor in descriptor.getAccessors()) { + (accessor as PropertyAccessorDescriptorImpl).setVisibility(Visibilities.PRIVATE_TO_THIS) + } + } + else { + throw IllegalStateException("Unexpected descriptor type: ${descriptor.javaClass.getName()}") + } + } + + private fun checkCallableDeclaration( + trace: BindingContext, + declaration: JetCallableDeclaration, + descriptor: CallableDescriptor, + diagnosticSink: DiagnosticSink + ): Boolean { + if (descriptor.getContainingDeclaration() !is ClassDescriptor) return true + var noError = true + + noError = noError and declaration.checkTypeParameters(trace, IN_VARIANCE, diagnosticSink) + + noError = noError and declaration.getReceiverTypeReference()?.checkTypePosition(trace, IN_VARIANCE, diagnosticSink) + + for (parameter in declaration.getValueParameters()) { + noError = noError and parameter.getTypeReference()?.checkTypePosition(trace, IN_VARIANCE, diagnosticSink) + } + + val returnTypePosition = if (descriptor is VariableDescriptor && descriptor.isVar()) INVARIANT else OUT_VARIANCE + noError = noError and declaration.createTypeBindingForReturnType(trace)?.checkTypePosition(returnTypePosition, diagnosticSink) + + return noError + } + + private fun JetTypeParameterListOwner.checkTypeParameters( + trace: BindingContext, + typePosition: Variance, + diagnosticSink: DiagnosticSink + ): Boolean { + var noError = true + for (typeParameter in getTypeParameters()) { + noError = noError and typeParameter.getExtendsBound()?.checkTypePosition(trace, typePosition, diagnosticSink) + } + for (typeConstraint in getTypeConstraints()) { + noError = noError and typeConstraint.getBoundTypeReference()?.checkTypePosition(trace, typePosition, diagnosticSink) + } + return noError + } + + private fun JetTypeReference.checkTypePosition(trace: BindingContext, position: Variance, diagnosticSink: DiagnosticSink) + = createTypeBinding(trace)?.checkTypePosition(position, diagnosticSink) + + private fun TypeBinding.checkTypePosition(position: Variance, diagnosticSink: DiagnosticSink) + = checkTypePosition(jetType, position, diagnosticSink) + + private fun TypeBinding.checkTypePosition(containingType: JetType, position: Variance, diagnosticSink: DiagnosticSink): Boolean { + val classifierDescriptor = jetType.getConstructor().getDeclarationDescriptor() + if (classifierDescriptor is TypeParameterDescriptor) { + val declarationVariance = classifierDescriptor.getVariance() + if (!declarationVariance.allowsPosition(position)) { + 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, diagnosticSink) + } + } + return noError + } + + private fun Boolean.and(other: Boolean?) = if (other == null) this else this and other + } +} diff --git a/core/descriptors/src/org/jetbrains/jet/lang/descriptors/Visibilities.java b/core/descriptors/src/org/jetbrains/jet/lang/descriptors/Visibilities.java index b2f75bb9313..1ce6fc3a03b 100644 --- a/core/descriptors/src/org/jetbrains/jet/lang/descriptors/Visibilities.java +++ b/core/descriptors/src/org/jetbrains/jet/lang/descriptors/Visibilities.java @@ -57,6 +57,8 @@ public class Visibilities { } }; + public static final Visibility PRIVATE_TO_THIS = PRIVATE; + public static final Visibility PROTECTED = new Visibility("protected", true) { @Override protected boolean isVisible(@NotNull DeclarationDescriptorWithVisibility what, @NotNull DeclarationDescriptor from) { @@ -174,4 +176,8 @@ public class Visibilities { } return null; } + + public static boolean isPrivate(@NotNull Visibility visibility) { + return visibility == PRIVATE || visibility == PRIVATE_TO_THIS; + } }