Inferring common supertypes taking projections and bounds into account

This commit is contained in:
Andrey Breslav
2011-02-01 17:31:56 +03:00
parent c6a60d8bf6
commit 08c1863981
16 changed files with 256 additions and 149 deletions
@@ -0,0 +1,8 @@
package org.jetbrains.jet.lang.psi;
/**
* @author abreslav
*/
public enum JetProjectionKind {
IN, OUT, STAR, NONE;
}
@@ -4,7 +4,6 @@ import com.intellij.lang.ASTNode;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.JetNodeTypes;
import org.jetbrains.jet.lang.types.ProjectionKind;
import org.jetbrains.jet.lexer.JetTokens;
/**
@@ -16,21 +15,21 @@ public class JetTypeProjection extends JetDeclaration {
}
@NotNull
public ProjectionKind getProjectionKind() {
public JetProjectionKind getProjectionKind() {
JetModifierList modifierList = getModifierList();
if (modifierList != null) {
if (modifierList.hasModifier(JetTokens.IN_KEYWORD)) {
return ProjectionKind.IN_ONLY;
return JetProjectionKind.IN;
}
if (modifierList.hasModifier(JetTokens.OUT_KEYWORD)) {
return ProjectionKind.OUT_ONLY;
return JetProjectionKind.OUT;
}
}
if (findChildByType(JetTokens.MUL) != null) {
return ProjectionKind.NEITHER_OUT_NOR_IN;
return JetProjectionKind.STAR;
}
return ProjectionKind.NO_PROJECTION;
return JetProjectionKind.NONE;
}
@Override
@@ -10,6 +10,7 @@ import org.jetbrains.jet.lang.types.ClassDescriptor;
import org.jetbrains.jet.lang.types.JetStandardClasses;
import org.jetbrains.jet.lang.types.Type;
import org.jetbrains.jet.lang.types.TypeParameterDescriptor;
import org.jetbrains.jet.lexer.JetTokens;
import java.util.*;
@@ -33,8 +34,10 @@ public class ClassDescriptorResolver {
Collection<? extends Type> superclasses = delegationSpecifiers.isEmpty()
? Collections.singleton(JetStandardClasses.getAnyType())
: resolveTypes(extensibleScope, delegationSpecifiers);
boolean open = classElement.getModifierList().hasModifier(JetTokens.OPEN_KEYWORD);
return new ClassDescriptor(
AttributeResolver.INSTANCE.resolveAttributes(classElement.getModifierList()),
!open,
classElement.getName(),
typeParameters,
superclasses
@@ -104,16 +104,29 @@ public class TypeResolver {
for (int i = 0, argumentElementsSize = argumentElements.size(); i < argumentElementsSize; i++) {
JetTypeProjection argumentElement = argumentElements.get(i);
ProjectionKind projectionKind = argumentElement.getProjectionKind();
JetProjectionKind projectionKind = argumentElement.getProjectionKind();
Type type;
if (projectionKind == ProjectionKind.NEITHER_OUT_NOR_IN) {
if (projectionKind == JetProjectionKind.STAR) {
Set<Type> upperBounds = constructor.getParameters().get(i).getUpperBounds();
arguments.add(new TypeProjection(ProjectionKind.OUT_ONLY, TypeUtils.intersect(upperBounds)));
arguments.add(new TypeProjection(Variance.OUT_VARIANCE, TypeUtils.intersect(upperBounds)));
}
else {
// TODO : handle the Foo<in *> case
type = resolveType(scope, argumentElement.getTypeReference());
arguments.add(new TypeProjection(projectionKind, type));
Variance kind = null;
switch (projectionKind) {
case IN:
kind = Variance.IN_VARIANCE;
break;
case OUT:
kind = Variance.OUT_VARIANCE;
break;
case NONE:
kind = Variance.INVARIANT;
break;
}
assert kind != null;
arguments.add(new TypeProjection(kind, type));
}
}
return arguments;
@@ -13,14 +13,14 @@ public class ClassDescriptor extends MemberDescriptorImpl implements MemberDomai
private final TypeConstructor typeConstructor;
public ClassDescriptor(
List<Attribute> attributes,
List<Attribute> attributes, boolean sealed,
String name, List<TypeParameterDescriptor> typeParameters, Collection<? extends Type> superclasses) {
super(attributes, name);
this.typeConstructor = new TypeConstructor(attributes, name, typeParameters, superclasses);
this.typeConstructor = new TypeConstructor(attributes, sealed, name, typeParameters, superclasses);
}
public ClassDescriptor(String name) {
this(Collections.<Attribute>emptyList(),
this(Collections.<Attribute>emptyList(), true,
name, Collections.<TypeParameterDescriptor>emptyList(),
Collections.<Type>singleton(JetStandardClasses.getAnyType()));
}
@@ -14,7 +14,7 @@ public class ErrorType {
throw new UnsupportedOperationException(); // TODO
}
};
private static final TypeConstructor ERROR = new TypeConstructor(Collections.<Attribute>emptyList(), "ERROR", Collections.<TypeParameterDescriptor>emptyList(), Collections.<Type>emptyList());
private static final TypeConstructor ERROR = new TypeConstructor(Collections.<Attribute>emptyList(), false, "ERROR", Collections.<TypeParameterDescriptor>emptyList(), Collections.<Type>emptyList());
public static Type createErrorType(String debugMessage) {
return new TypeImpl(ERROR, ERROR_DOMAIN);
@@ -11,6 +11,7 @@ public class JetStandardClasses {
private static ClassDescriptor NOTHING_CLASS = new ClassDescriptor(
Collections.<Attribute>emptyList(),
true,
"Nothing",
Collections.<TypeParameterDescriptor>emptyList(),
new AbstractCollection<Type>() {
@@ -33,6 +34,7 @@ public class JetStandardClasses {
private static final ClassDescriptor ANY = new ClassDescriptor(
Collections.<Attribute>emptyList(),
false,
"Any",
Collections.<TypeParameterDescriptor>emptyList(),
Collections.<Type>emptySet()
@@ -64,6 +66,7 @@ public class JetStandardClasses {
}
TUPLE[i] = new ClassDescriptor(
Collections.<Attribute>emptyList(),
true,
"Tuple" + i,
parameters,
Collections.singleton(JetStandardClasses.getAnyType()));
@@ -244,7 +247,7 @@ public class JetStandardClasses {
private static List<TypeProjection> toProjections(List<Type> arguments) {
List<TypeProjection> result = new ArrayList<TypeProjection>();
for (Type argument : arguments) {
result.add(new TypeProjection(ProjectionKind.OUT_ONLY, argument));
result.add(new TypeProjection(Variance.OUT_VARIANCE, argument));
}
return result;
}
@@ -216,7 +216,6 @@ public class JetTypeChecker {
}
private TypeProjection computeSupertypeProjection(TypeParameterDescriptor parameterDescriptor, Set<TypeProjection> typeProjections) {
// TODO: equals/hashCode for projections
if (typeProjections.size() == 1) {
return typeProjections.iterator().next();
}
@@ -238,8 +237,8 @@ public class JetTypeChecker {
}
for (TypeProjection projection : typeProjections) {
ProjectionKind projectionKind = projection.getProjectionKind();
if (projectionKind.allowsInCalls()) {
Variance projectionKind = projection.getProjectionKind();
if (projectionKind.allowsInPosition()) {
if (ins != null) {
ins.add(projection.getType());
}
@@ -247,7 +246,7 @@ public class JetTypeChecker {
ins = null;
}
if (projectionKind.allowsOutCalls()) {
if (projectionKind.allowsOutPosition()) {
if (outs != null) {
outs.add(projection.getType());
}
@@ -257,13 +256,17 @@ public class JetTypeChecker {
}
if (ins != null) {
ProjectionKind projectionKind = variance == Variance.IN_VARIANCE ? ProjectionKind.NO_PROJECTION : ProjectionKind.IN_ONLY;
return new TypeProjection(projectionKind, TypeUtils.intersect(ins));
Variance projectionKind = variance == Variance.IN_VARIANCE ? Variance.INVARIANT : Variance.IN_VARIANCE;
Type intersection = TypeUtils.intersect(ins);
if (intersection == null) {
return new TypeProjection(Variance.OUT_VARIANCE, commonSupertype(parameterDescriptor.getUpperBounds()));
}
return new TypeProjection(projectionKind, intersection);
} else if (outs != null) {
ProjectionKind projectionKind = variance == Variance.OUT_VARIANCE ? ProjectionKind.NO_PROJECTION : ProjectionKind.OUT_ONLY;
Variance projectionKind = variance == Variance.OUT_VARIANCE ? Variance.INVARIANT : Variance.OUT_VARIANCE;
return new TypeProjection(projectionKind, commonSupertype(outs));
} else {
ProjectionKind projectionKind = variance == Variance.OUT_VARIANCE ? ProjectionKind.NO_PROJECTION : ProjectionKind.OUT_ONLY;
Variance projectionKind = variance == Variance.OUT_VARIANCE ? Variance.INVARIANT : Variance.OUT_VARIANCE;
return new TypeProjection(projectionKind, commonSupertype(parameterDescriptor.getUpperBounds()));
}
}
@@ -417,17 +420,13 @@ public class JetTypeChecker {
@NotNull
private TypeProjection substitute(Map<TypeConstructor, TypeProjection> parameterValues, TypeProjection subject) {
ProjectionKind projectionKind = subject.getProjectionKind();
if (projectionKind == ProjectionKind.NEITHER_OUT_NOR_IN) {
return subject;
}
@NotNull Type subjectType = subject.getType();
TypeProjection value = parameterValues.get(subjectType.getConstructor());
if (value != null) {
return value;
}
List<TypeProjection> newArguments = substituteInArguments(parameterValues, subjectType);
return new TypeProjection(projectionKind, specializeType(subjectType, newArguments));
return new TypeProjection(subject.getProjectionKind(), specializeType(subjectType, newArguments));
}
private List<TypeProjection> substituteInArguments(Map<TypeConstructor, TypeProjection> parameterValues, Type subjectType) {
@@ -456,62 +455,58 @@ public class JetTypeChecker {
Type subArgumentType = subArgument.getType();
Type superArgumentType = superArgument.getType();
if (superArgument.getProjectionKind() != ProjectionKind.NEITHER_OUT_NOR_IN) {
switch (parameter.getVariance()) {
case INVARIANT:
switch (superArgument.getProjectionKind()) {
case NO_PROJECTION:
if (!equalTypes(subArgumentType, superArgumentType)) {
return false;
}
break;
case OUT_ONLY:
if (!subArgument.getProjectionKind().allowsOutCalls()) {
return false;
}
if (!isSubtypeOf(subArgumentType, superArgumentType)) {
return false;
}
break;
case IN_ONLY:
if (!subArgument.getProjectionKind().allowsInCalls()) {
return false;
}
if (!isSubtypeOf(superArgumentType, subArgumentType)) {
return false;
}
break;
}
break;
case IN_VARIANCE:
switch (superArgument.getProjectionKind()) {
case NO_PROJECTION:
case IN_ONLY:
if (!isSubtypeOf(superArgumentType, subArgumentType)) {
return false;
}
break;
case OUT_ONLY:
if (!isSubtypeOf(subArgumentType, superArgumentType)) {
return false;
}
break;
}
break;
case OUT_VARIANCE:
switch (superArgument.getProjectionKind()) {
case NO_PROJECTION:
case OUT_ONLY:
case IN_ONLY:
if (!isSubtypeOf(subArgumentType, superArgumentType)) {
return false;
}
break;
}
break;
}
} else {
// C< anything > is always a subtype of C<*>
switch (parameter.getVariance()) {
case INVARIANT:
switch (superArgument.getProjectionKind()) {
case INVARIANT:
if (!equalTypes(subArgumentType, superArgumentType)) {
return false;
}
break;
case OUT_VARIANCE:
if (!subArgument.getProjectionKind().allowsOutPosition()) {
return false;
}
if (!isSubtypeOf(subArgumentType, superArgumentType)) {
return false;
}
break;
case IN_VARIANCE:
if (!subArgument.getProjectionKind().allowsInPosition()) {
return false;
}
if (!isSubtypeOf(superArgumentType, subArgumentType)) {
return false;
}
break;
}
break;
case IN_VARIANCE:
switch (superArgument.getProjectionKind()) {
case INVARIANT:
case IN_VARIANCE:
if (!isSubtypeOf(superArgumentType, subArgumentType)) {
return false;
}
break;
case OUT_VARIANCE:
if (!isSubtypeOf(subArgumentType, superArgumentType)) {
return false;
}
break;
}
break;
case OUT_VARIANCE:
switch (superArgument.getProjectionKind()) {
case INVARIANT:
case OUT_VARIANCE:
case IN_VARIANCE:
if (!isSubtypeOf(subArgumentType, superArgumentType)) {
return false;
}
break;
}
break;
}
}
return true;
@@ -1,35 +0,0 @@
package org.jetbrains.jet.lang.types;
/**
* @author abreslav
*/
public enum ProjectionKind {
OUT_ONLY("out", false, true),
IN_ONLY("in", true, false),
NEITHER_OUT_NOR_IN("*", false, false),
NO_PROJECTION("", true, true);
private final String text;
private final boolean allowsInCalls;
private final boolean allowsOutCalls;
ProjectionKind(String text, boolean allowsInCalls, boolean allowsOutCalls) {
this.text = text;
this.allowsInCalls = allowsInCalls;
this.allowsOutCalls = allowsOutCalls;
}
public boolean allowsInCalls() {
return allowsInCalls;
}
public boolean allowsOutCalls() {
return allowsOutCalls;
}
@Override
public String toString() {
return text;
}
}
@@ -10,9 +10,11 @@ public class TypeConstructor extends AnnotatedImpl {
private final List<TypeParameterDescriptor> parameters;
private final Collection<? extends Type> supertypes;
private final String debugName;
private final boolean sealed;
public TypeConstructor(List<Attribute> attributes, String debugName, List<TypeParameterDescriptor> parameters, Collection<? extends Type> supertypes) {
public TypeConstructor(List<Attribute> attributes, boolean sealed, String debugName, List<TypeParameterDescriptor> parameters, Collection<? extends Type> supertypes) {
super(attributes);
this.sealed = sealed;
this.debugName = debugName;
this.parameters = parameters;
this.supertypes = supertypes;
@@ -30,4 +32,8 @@ public class TypeConstructor extends AnnotatedImpl {
public String toString() {
return debugName;
}
public boolean isSealed() {
return sealed;
}
}
@@ -109,10 +109,8 @@ public final class TypeImpl extends AnnotatedImpl implements Type {
if (typeProjection1.getProjectionKind() != typeProjection2.getProjectionKind()) {
return false;
}
if (typeProjection1.getProjectionKind() != ProjectionKind.NEITHER_OUT_NOR_IN) {
if (!equalTypes(typeProjection1.getType(), typeProjection2.getType())) {
return false;
}
if (!equalTypes(typeProjection1.getType(), typeProjection2.getType())) {
return false;
}
}
return true;
@@ -19,6 +19,7 @@ public class TypeParameterDescriptor extends NamedAnnotatedImpl {
// TODO: Should we actually pass the attributes on to the type constructor?
this.typeConstructor = new TypeConstructor(
attributes,
false,
"&" + name,
Collections.<TypeParameterDescriptor>emptyList(),
upperBounds);
@@ -1,22 +1,24 @@
package org.jetbrains.jet.lang.types;
import org.jetbrains.annotations.NotNull;
/**
* @author abreslav
*/
public class TypeProjection {
private final ProjectionKind projection;
private final Variance projection;
private final Type type;
public TypeProjection(ProjectionKind projection, Type type) {
public TypeProjection(@NotNull Variance projection, @NotNull Type type) {
this.projection = projection;
this.type = type;
}
public TypeProjection(Type type) {
this(ProjectionKind.NO_PROJECTION, type);
this(Variance.INVARIANT, type);
}
public ProjectionKind getProjectionKind() {
public Variance getProjectionKind() {
return projection;
}
@@ -26,12 +28,29 @@ public class TypeProjection {
@Override
public String toString() {
if (projection == ProjectionKind.NEITHER_OUT_NOR_IN) {
return projection.toString();
}
if (projection == ProjectionKind.NO_PROJECTION) {
if (projection == Variance.INVARIANT) {
return type + "";
}
return projection + " " + type;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TypeProjection that = (TypeProjection) o;
if (projection != that.projection) return false;
if (type != null ? !type.equals(that.type) : that.type != null) return false;
return true;
}
@Override
public int hashCode() {
int result = projection != null ? projection.hashCode() : 0;
result = 31 * result + (type != null ? type.hashCode() : 0);
return result;
}
}
@@ -1,5 +1,7 @@
package org.jetbrains.jet.lang.types;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -16,6 +18,7 @@ public class TypeUtils {
return new TypeImpl(type.getAttributes(), type.getConstructor(), true, type.getArguments(), type.getMemberDomain());
}
@Nullable
public static Type intersect(Set<Type> types) {
assert !types.isEmpty();
@@ -28,6 +31,15 @@ public class TypeUtils {
for (Iterator<Type> iterator = types.iterator(); iterator.hasNext();) {
Type type = iterator.next();
if (!canHaveSubtypes(type)) {
for (Type other : types) {
if (type != other || !JetTypeChecker.INSTANCE.isSubtypeOf(type, other)) {
return null;
}
}
return type;
}
nullable |= type.isNullable();
debugName.append(type.toString());
@@ -37,7 +49,7 @@ public class TypeUtils {
}
List<Attribute> noAttributes = Collections.<Attribute>emptyList();
TypeConstructor constructor = new TypeConstructor(noAttributes, debugName.toString(), Collections.<TypeParameterDescriptor>emptyList(), types);
TypeConstructor constructor = new TypeConstructor(noAttributes, false, debugName.toString(), Collections.<TypeParameterDescriptor>emptyList(), types);
return new TypeImpl(
noAttributes,
constructor,
@@ -46,6 +58,79 @@ public class TypeUtils {
JetStandardClasses.STUB);
}
private static boolean canHaveSubtypes(Type type) {
// TODO : a nullable type can have a subtype -- a non-nullable version
if (!type.getConstructor().isSealed()) {
return true;
}
List<TypeParameterDescriptor> parameters = type.getConstructor().getParameters();
List<TypeProjection> arguments = type.getArguments();
for (int i = 0, parametersSize = parameters.size(); i < parametersSize; i++) {
TypeParameterDescriptor parameterDescriptor = parameters.get(i);
TypeProjection typeProjection = arguments.get(i);
Variance projectionKind = typeProjection.getProjectionKind();
Type argument = typeProjection.getType();
switch (parameterDescriptor.getVariance()) {
case INVARIANT:
switch (projectionKind) {
case INVARIANT:
if (lowerThanBound(argument, parameterDescriptor) || canHaveSubtypes(argument)) {
return true;
}
break;
case IN_VARIANCE:
if (lowerThanBound(argument, parameterDescriptor)) {
return true;
}
break;
case OUT_VARIANCE:
if (canHaveSubtypes(argument)) {
return true;
}
break;
}
break;
case IN_VARIANCE:
if (projectionKind != Variance.OUT_VARIANCE) {
if (lowerThanBound(argument, parameterDescriptor)) {
return true;
}
} else {
if (canHaveSubtypes(argument)) {
return true;
}
}
break;
case OUT_VARIANCE:
if (projectionKind != Variance.IN_VARIANCE) {
if (canHaveSubtypes(argument)) {
return true;
}
} else {
if (lowerThanBound(argument, parameterDescriptor)) {
return true;
}
}
break;
}
}
return false;
}
private static boolean lowerThanBound(Type argument, TypeParameterDescriptor parameterDescriptor) {
for (Type bound : parameterDescriptor.getUpperBounds()) {
if (JetTypeChecker.INSTANCE.isSubtypeOf(argument, bound)) {
if (!argument.getConstructor().equals(bound.getConstructor())) {
return true;
}
}
}
return false;
}
public static Type makeNullableIfNeeded(Type type, boolean nullable) {
if (nullable) {
return makeNullable(type);
@@ -4,14 +4,26 @@ package org.jetbrains.jet.lang.types;
* @author max
*/
public enum Variance {
INVARIANT(""),
IN_VARIANCE("in"),
OUT_VARIANCE("out");
INVARIANT("", true, true),
IN_VARIANCE("in", true, false),
OUT_VARIANCE("out", false, true);
private final String label;
private final boolean allowsInPosition;
private final boolean allowsOutPosition;
Variance(String label) {
Variance(String label, boolean allowsInPosition, boolean allowsOutPosition) {
this.label = label;
this.allowsInPosition = allowsInPosition;
this.allowsOutPosition = allowsOutPosition;
}
public boolean allowsInPosition() {
return allowsInPosition;
}
public boolean allowsOutPosition() {
return allowsOutPosition;
}
@Override
@@ -93,10 +93,10 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase {
assertType("if (true) null : DDerived_T<Int>? else null : Derived_T<Int>?", "Derived_T<Int>?");
assertType("if (true) null : DDerived_T<Int>? else null : DDerived1_T<Int>?", "Derived_T<Int>?");
assertType("if (true) null : Base_T<Int>? else null : Base_T<Boolean>?", "Any?");
assertType("if (true) null : Base_T<Int>? else null : Base_T<Boolean>?", "Base_T<out Any>?");
assertType("if (true) null : Base_T<Int>? else null : Base_T<in Int>?", "Base_T<in Int>?");
assertType("if (true) null : Derived_T<Int>? else null : Base_T<in Int>?", "Base_T<in Int>?");
assertType("if (true) null : Derived_T<in Int>? else null : Base_T<Int>?", "Any?");
assertType("if (true) null : Derived_T<in Int>? else null : Base_T<Int>?", "Base_T<in Int>?");
assertType("if (true) null : Base_T<Int>? else null : Base_T<*>?", "Base_T<*>?");
}
@@ -274,7 +274,7 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase {
Project project = getProject();
JetExpression jetExpression = JetChangeUtil.createExpression(project, expression);
Type type = JetTypeChecker.INSTANCE.getType(ClassDefinitions.BASIC_SCOPE, jetExpression);
assertTrue(type + "!=" + expectedType, JetTypeChecker.INSTANCE.equalTypes(type, expectedType));
assertTrue(type + " != " + expectedType, JetTypeChecker.INSTANCE.equalTypes(type, expectedType));
}
private static void assertType(String expression, String expectedTypeStr) {
@@ -282,7 +282,7 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase {
JetExpression jetExpression = JetChangeUtil.createExpression(project, expression);
Type type = JetTypeChecker.INSTANCE.getType(ClassDefinitions.BASIC_SCOPE, jetExpression);
Type expectedType = makeType(expectedTypeStr);
assertTrue(type + "!=" + expectedType, JetTypeChecker.INSTANCE.equalTypes(type, expectedType));
assertTrue(type + " != " + expectedType, JetTypeChecker.INSTANCE.equalTypes(type, expectedType));
}
private static Type makeType(String typeStr) {
@@ -292,14 +292,14 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase {
private static class ClassDefinitions {
private static Map<String, ClassDescriptor> CLASSES = new HashMap<String, ClassDescriptor>();
private static String[] CLASS_DECLARATIONS = {
"class Base_T<T>",
"class Derived_T<T> : Base_T<T>",
"class DDerived_T<T> : Derived_T<T>",
"class DDerived1_T<T> : Derived_T<T>",
"class Base_inT<in T>",
"class Derived_inT<in T> : Base_inT<T>",
"class Base_outT<out T>",
"class Derived_outT<out T> : Base_outT<T>",
"open class Base_T<T>",
"open class Derived_T<T> : Base_T<T>",
"open class DDerived_T<T> : Derived_T<T>",
"open class DDerived1_T<T> : Derived_T<T>",
"open class Base_inT<in T>",
"open class Derived_inT<in T> : Base_inT<T>",
"open class Base_outT<out T>",
"open class Derived_outT<out T> : Base_outT<T>",
};
public static JetScope BASIC_SCOPE = new JetScopeImpl() {