Create VarianceChecker

This commit is contained in:
Stanislav Erokhin
2014-11-18 12:59:42 +03:00
parent a8be80328f
commit c2d99a40cd
7 changed files with 238 additions and 3 deletions
@@ -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<JetTypeReference, Integer, String> NO_TYPE_ARGUMENTS_ON_RHS = DiagnosticFactory2.create(ERROR);
DiagnosticFactory1<JetTypeProjection, ClassifierDescriptor> CONFLICTING_PROJECTION = DiagnosticFactory1.create(ERROR, VARIANCE_IN_PROJECTION);
DiagnosticFactory1<JetTypeProjection, ClassifierDescriptor> REDUNDANT_PROJECTION = DiagnosticFactory1.create(WARNING, VARIANCE_IN_PROJECTION);
DiagnosticFactory1<PsiElement, VarianceConflictDiagnosticData> TYPE_VARIANCE_CONFLICT =
DiagnosticFactory1.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -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<VarianceConflictDiagnosticData>() {
@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);
@@ -131,6 +131,19 @@ public class Renderers {
}
};
public static final Renderer<Variance> RENDER_POSITION_VARIANCE = new Renderer<Variance>() {
@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<Collection<? extends ResolvedCall<?>>> AMBIGUOUS_CALLS =
new Renderer<Collection<? extends ResolvedCall<?>>>() {
@NotNull
@@ -939,7 +939,7 @@ public class OverrideResolver {
}
private void checkVisibility(@NotNull TopDownAnalysisContext c) {
for (Map.Entry<JetDeclaration, CallableMemberDescriptor> entry : c.getMembers().entrySet()) {
for (Map.Entry<JetCallableDeclaration, CallableMemberDescriptor> entry : c.getMembers().entrySet()) {
checkVisibilityForMember(entry.getKey(), entry.getValue());
}
}
@@ -51,7 +51,7 @@ public class TopDownAnalysisContext implements BodiesResolveContext {
private final Map<JetNamedFunction, SimpleFunctionDescriptor> functions = Maps.newLinkedHashMap();
private final Map<JetProperty, PropertyDescriptor> properties = Maps.newLinkedHashMap();
private final Map<JetParameter, PropertyDescriptor> primaryConstructorParameterProperties = Maps.newHashMap();
private Map<JetDeclaration, CallableMemberDescriptor> members = null;
private Map<JetCallableDeclaration, CallableMemberDescriptor> members = null;
// File scopes - package scope extended with imports
protected final Map<JetFile, WritableScope> fileScopes = Maps.newHashMap();
@@ -173,7 +173,8 @@ public class TopDownAnalysisContext implements BodiesResolveContext {
return functions;
}
public Map<JetDeclaration, CallableMemberDescriptor> getMembers() {
@NotNull
public Map<JetCallableDeclaration, CallableMemberDescriptor> getMembers() {
if (members == null) {
members = Maps.newHashMap();
members.putAll(functions);
@@ -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<PsiElement>.checkTypePosition(position: Variance, diagnosticSink: DiagnosticSink)
= checkTypePosition(jetType, position, diagnosticSink)
private fun TypeBinding<PsiElement>.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
}
}
@@ -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;
}
}