Working on the typechecker: inferring common supertypes taking projections and bounds into account

This commit is contained in:
Andrey Breslav
2011-01-31 20:00:17 +03:00
parent b70fcfb3f6
commit c6a60d8bf6
8 changed files with 339 additions and 78 deletions
@@ -9,6 +9,7 @@ import org.jetbrains.jet.lang.types.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* @author abreslav
@@ -35,11 +36,12 @@ public class TypeResolver {
public void visitUserType(JetUserType type) {
ClassDescriptor classDescriptor = resolveClass(scope, type);
if (classDescriptor != null) {
TypeConstructor typeConstructor = classDescriptor.getTypeConstructor();
result[0] = new TypeImpl(
attributes,
classDescriptor.getTypeConstructor(),
typeConstructor,
nullable,
resolveTypeProjections(scope, type.getTypeArguments()),
resolveTypeProjections(scope, typeConstructor, type.getTypeArguments()),
JetStandardClasses.STUB
);
}
@@ -97,18 +99,22 @@ public class TypeResolver {
}
@NotNull
private List<TypeProjection> resolveTypeProjections(JetScope scope, List<JetTypeProjection> argumentElements) {
private List<TypeProjection> resolveTypeProjections(JetScope scope, TypeConstructor constructor, List<JetTypeProjection> argumentElements) {
final List<TypeProjection> arguments = new ArrayList<TypeProjection>();
for (JetTypeProjection argumentElement : argumentElements) {
for (int i = 0, argumentElementsSize = argumentElements.size(); i < argumentElementsSize; i++) {
JetTypeProjection argumentElement = argumentElements.get(i);
ProjectionKind projectionKind = argumentElement.getProjectionKind();
Type type;
if (projectionKind == ProjectionKind.NEITHER_OUT_NOR_IN) {
type = null;
} else {
type = resolveType(scope, argumentElement.getTypeReference());
Set<Type> upperBounds = constructor.getParameters().get(i).getUpperBounds();
arguments.add(new TypeProjection(ProjectionKind.OUT_ONLY, TypeUtils.intersect(upperBounds)));
}
else {
// TODO : handle the Foo<in *> case
type = resolveType(scope, argumentElement.getTypeReference());
arguments.add(new TypeProjection(projectionKind, type));
}
TypeProjection typeProjection = new TypeProjection(projectionKind, type);
arguments.add(typeProjection);
}
return arguments;
}
@@ -0,0 +1,19 @@
package org.jetbrains.jet.lang.types;
/**
* @author abreslav
*/
public class DfsNodeHandler<R> {
public void beforeChildren(Type current) {
}
public void afterChildren(Type current) {
}
public R result() {
return null;
}
}
@@ -39,6 +39,7 @@ public class JetStandardClasses {
);
private static final Type ANY_TYPE = new TypeImpl(ANY.getTypeConstructor(), TypeMemberDomain.EMPTY);
private static final Type NULLABLE_ANY_TYPE = TypeUtils.makeNullable(ANY_TYPE);
private static final ClassDescriptor BYTE = new ClassDescriptor("Byte");
private static final ClassDescriptor CHAR = new ClassDescriptor("Char");
@@ -104,6 +105,10 @@ public class JetStandardClasses {
return ANY_TYPE;
}
public static Type getNullableAnyType() {
return NULLABLE_ANY_TYPE;
}
@NotNull
public static ClassDescriptor getByte() {
return BYTE;
@@ -127,7 +127,7 @@ public class JetTypeChecker {
} else {
Type thenType = getType(scope, expression.getThen());
Type elseType = getType(scope, elseBranch);
result[0] = commonSupertype(thenType, elseType);
result[0] = commonSupertype(Arrays.asList(thenType, elseType));
}
}
@@ -150,53 +150,213 @@ public class JetTypeChecker {
return result[0];
}
private Type commonSupertype(Type thenType, Type elseType) {
if (JetStandardClasses.isNothing(thenType)) {
return elseType;
}
if (JetStandardClasses.isNothing(elseType)) {
return thenType;
private Type commonSupertype(Collection<Type> types) {
Collection<Type> typeSet = new HashSet<Type>(types);
assert !typeSet.isEmpty();
boolean nullable = false;
for (Iterator<Type> iterator = typeSet.iterator(); iterator.hasNext();) {
Type type = iterator.next();
if (JetStandardClasses.isNothing(type)) {
iterator.remove();
}
nullable |= type.isNullable();
}
List<Type> thenOrder = new LinkedList<Type>(); // adding to the beginning
Map<TypeConstructor, Type> visited = new HashMap<TypeConstructor, Type>();
topologicallySort(thenType, visited, thenOrder, null);
List<Type> elseOrder = new LinkedList<Type>(); // adding to the beginning
topologicallySort(elseType, new HashMap<TypeConstructor, Type>(), elseOrder, visited);
assert !elseOrder.isEmpty() : "No common supertype";
// TODO: support multiple common supertypes
Type result = elseOrder.get(0);
if (thenType.isNullable() || elseType.isNullable()) {
return TypeUtils.makeNullable(result);
if (typeSet.isEmpty()) {
// TODO : attributes
return nullable ? JetStandardClasses.getNullableNothingType() : JetStandardClasses.getNothingType();
}
assert !result.isNullable();
if (typeSet.size() == 1) {
return TypeUtils.makeNullableIfNeeded(typeSet.iterator().next(), nullable);
}
Map<TypeConstructor, Set<Type>> commonSupertypes = computeCommonRawSupertypes(typeSet);
while (commonSupertypes.size() > 1) {
HashSet<Type> merge = new HashSet<Type>();
for (Set<Type> supertypes : commonSupertypes.values()) {
merge.addAll(supertypes);
}
commonSupertypes = computeCommonRawSupertypes(merge);
}
assert !commonSupertypes.isEmpty();
Map.Entry<TypeConstructor, Set<Type>> entry = commonSupertypes.entrySet().iterator().next();
Type result = computeSupertypeProjections(entry.getKey(), entry.getValue());
return TypeUtils.makeNullableIfNeeded(result, nullable);
}
private Type computeSupertypeProjections(TypeConstructor constructor, Set<Type> types) {
// we assume that all the given types are applications of the same type constructor
assert !types.isEmpty();
if (types.size() == 1) {
return types.iterator().next();
}
List<TypeParameterDescriptor> parameters = constructor.getParameters();
List<TypeProjection> newProjections = new ArrayList<TypeProjection>();
for (int i = 0, parametersSize = parameters.size(); i < parametersSize; i++) {
TypeParameterDescriptor parameterDescriptor = parameters.get(i);
Set<TypeProjection> typeProjections = new HashSet<TypeProjection>();
for (Type type : types) {
typeProjections.add(type.getArguments().get(i));
}
newProjections.add(computeSupertypeProjection(parameterDescriptor, typeProjections));
}
boolean nullable = false;
for (Type type : types) {
nullable |= type.isNullable();
}
// TODO : attributes?
return new TypeImpl(Collections.<Attribute>emptyList(), constructor, nullable, newProjections, JetStandardClasses.STUB);
}
private TypeProjection computeSupertypeProjection(TypeParameterDescriptor parameterDescriptor, Set<TypeProjection> typeProjections) {
// TODO: equals/hashCode for projections
if (typeProjections.size() == 1) {
return typeProjections.iterator().next();
}
Set<Type> ins = new HashSet<Type>();
Set<Type> outs = new HashSet<Type>();
Variance variance = parameterDescriptor.getVariance();
switch (variance) {
case INVARIANT:
// Nothing
break;
case IN_VARIANCE:
outs = null;
break;
case OUT_VARIANCE:
ins = null;
break;
}
for (TypeProjection projection : typeProjections) {
ProjectionKind projectionKind = projection.getProjectionKind();
if (projectionKind.allowsInCalls()) {
if (ins != null) {
ins.add(projection.getType());
}
} else {
ins = null;
}
if (projectionKind.allowsOutCalls()) {
if (outs != null) {
outs.add(projection.getType());
}
} else {
outs = null;
}
}
if (ins != null) {
ProjectionKind projectionKind = variance == Variance.IN_VARIANCE ? ProjectionKind.NO_PROJECTION : ProjectionKind.IN_ONLY;
return new TypeProjection(projectionKind, TypeUtils.intersect(ins));
} else if (outs != null) {
ProjectionKind projectionKind = variance == Variance.OUT_VARIANCE ? ProjectionKind.NO_PROJECTION : ProjectionKind.OUT_ONLY;
return new TypeProjection(projectionKind, commonSupertype(outs));
} else {
ProjectionKind projectionKind = variance == Variance.OUT_VARIANCE ? ProjectionKind.NO_PROJECTION : ProjectionKind.OUT_ONLY;
return new TypeProjection(projectionKind, commonSupertype(parameterDescriptor.getUpperBounds()));
}
}
private Map<TypeConstructor, Set<Type>> computeCommonRawSupertypes(Collection<Type> types) {
assert !types.isEmpty();
final Map<TypeConstructor, Set<Type>> constructorToAllInstances = new HashMap<TypeConstructor, Set<Type>>();
Set<TypeConstructor> commonSuperclasses = null;
List<TypeConstructor> order = null;
for (Iterator<Type> iterator = types.iterator(); iterator.hasNext();) {
Type type = iterator.next();
Set<TypeConstructor> visited = new HashSet<TypeConstructor>();
order = dfs(type, visited, new DfsNodeHandler<List<TypeConstructor>>() {
public LinkedList<TypeConstructor> list = new LinkedList<TypeConstructor>();
@Override
public void beforeChildren(Type current) {
TypeConstructor constructor = current.getConstructor();
Set<Type> instances = constructorToAllInstances.get(constructor);
if (instances == null) {
instances = new HashSet<Type>();
constructorToAllInstances.put(constructor, instances);
}
instances.add(current);
}
@Override
public void afterChildren(Type current) {
list.addFirst(current.getConstructor());
}
@Override
public List<TypeConstructor> result() {
return list;
}
});
if (commonSuperclasses == null) {
commonSuperclasses = visited;
} else {
commonSuperclasses.retainAll(visited);
}
}
assert order != null;
Set<TypeConstructor> notSource = new HashSet<TypeConstructor>();
Map<TypeConstructor, Set<Type>> result = new HashMap<TypeConstructor, Set<Type>>();
for (TypeConstructor superConstructor : order) {
if (!commonSuperclasses.contains(superConstructor)) {
continue;
}
if (!notSource.contains(superConstructor)) {
result.put(superConstructor, constructorToAllInstances.get(superConstructor));
markAll(superConstructor, notSource);
}
}
return result;
}
private void topologicallySort(Type current, Map<TypeConstructor, Type> visited, List<Type> topologicalOrder, @Nullable Map<TypeConstructor, Type> filter) {
Type visitedOccurrence = visited.put(current.getConstructor(), current);
if (visitedOccurrence != null) {
assert equalTypes(visitedOccurrence, current);
private void markAll(TypeConstructor typeConstructor, Set<TypeConstructor> markerSet) {
markerSet.add(typeConstructor);
for (Type type : typeConstructor.getSupertypes()) {
markAll(type.getConstructor(), markerSet);
}
}
private <R> R dfs(Type current, Set<TypeConstructor> visited, DfsNodeHandler<R> handler) {
doDfs(current, visited, handler);
return handler.result();
}
private void doDfs(Type current, Set<TypeConstructor> visited, DfsNodeHandler<?> handler) {
if (!visited.add(current.getConstructor())) {
return;
}
handler.beforeChildren(current);
Map<TypeConstructor, TypeProjection> substitutionContext = getSubstitutionContext(current);
for (Type supertype : current.getConstructor().getSupertypes()) {
TypeConstructor supertypeConstructor = supertype.getConstructor();
if (visited.containsKey(supertypeConstructor)) {
assert equalTypes(visited.get(supertypeConstructor), supertype);
if (visited.contains(supertypeConstructor)) {
continue;
}
Type substitutedSupertype = substituteInType(substitutionContext, supertype);
topologicallySort(substitutedSupertype, visited, topologicalOrder, filter);
}
if (filter != null && filter.containsKey(current.getConstructor())) {
assert equalTypes(filter.get(current.getConstructor()), current) : filter.get(current.getConstructor()) + " != " + current;
topologicalOrder.add(0, current);
dfs(substitutedSupertype, visited, handler);
}
handler.afterChildren(current);
}
public boolean isConvertibleTo(JetExpression expression, Type type) {
@@ -358,27 +518,7 @@ public class JetTypeChecker {
}
public boolean equalTypes(@NotNull Type type1, @NotNull Type type2) {
if (!type1.getConstructor().equals(type2.getConstructor())) {
return false;
}
List<TypeProjection> type1Arguments = type1.getArguments();
List<TypeProjection> type2Arguments = type2.getArguments();
if (type1Arguments.size() != type2Arguments.size()) {
return false;
}
for (int i = 0; i < type1Arguments.size(); i++) {
TypeProjection typeProjection1 = type1Arguments.get(i);
TypeProjection typeProjection2 = type2Arguments.get(i);
if (typeProjection1.getProjectionKind() != typeProjection2.getProjectionKind()) {
return false;
}
if (typeProjection1.getProjectionKind() != ProjectionKind.NEITHER_OUT_NOR_IN) {
if (!equalTypes(typeProjection1.getType(), typeProjection2.getType())) {
return false;
}
}
}
return true;
return TypeImpl.equalTypes(type1, type2);
}
}
@@ -13,6 +13,4 @@ public interface Type extends Annotated {
boolean isNullable();
@NotNull TypeMemberDomain getMemberDomain();
@NotNull Type getNullableVersion();
}
@@ -1,5 +1,7 @@
package org.jetbrains.jet.lang.types;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -26,15 +28,6 @@ public final class TypeImpl extends AnnotatedImpl implements Type {
this(Collections.<Attribute>emptyList(), constructor, false, Collections.<TypeProjection>emptyList(), memberDomain);
}
@Override
public Type getNullableVersion() {
if (isNullable()) {
return this;
}
// TODO: cache these?
return new TypeImpl(getAttributes(), constructor, true, arguments, memberDomain);
}
@Override
public TypeConstructor getConstructor() {
return constructor;
@@ -72,4 +65,58 @@ public final class TypeImpl extends AnnotatedImpl implements Type {
return stringBuilder;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TypeImpl type = (TypeImpl) o;
// TODO
return equalTypes(this, type);
// if (nullable != type.nullable) return false;
// if (arguments != null ? !arguments.equals(type.arguments) : type.arguments != null) return false;
// if (constructor != null ? !constructor.equals(type.constructor) : type.constructor != null) return false;
// if (memberDomain != null ? !memberDomain.equals(type.memberDomain) : type.memberDomain != null) return false;
// return true;
}
@Override
public int hashCode() {
int result = constructor != null ? constructor.hashCode() : 0;
result = 31 * result + (arguments != null ? arguments.hashCode() : 0);
result = 31 * result + (nullable ? 1 : 0);
return result;
}
public static boolean equalTypes(@NotNull Type type1, @NotNull Type type2) {
if (type1.isNullable() != type2.isNullable()) {
return false;
}
if (!type1.getConstructor().equals(type2.getConstructor())) {
return false;
}
List<TypeProjection> type1Arguments = type1.getArguments();
List<TypeProjection> type2Arguments = type2.getArguments();
if (type1Arguments.size() != type2Arguments.size()) {
return false;
}
for (int i = 0; i < type1Arguments.size(); i++) {
TypeProjection typeProjection1 = type1Arguments.get(i);
TypeProjection typeProjection2 = type2Arguments.get(i);
if (typeProjection1.getProjectionKind() != typeProjection2.getProjectionKind()) {
return false;
}
if (typeProjection1.getProjectionKind() != ProjectionKind.NEITHER_OUT_NOR_IN) {
if (!equalTypes(typeProjection1.getType(), typeProjection2.getType())) {
return false;
}
}
}
return true;
}
}
@@ -1,18 +1,18 @@
package org.jetbrains.jet.lang.types;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* @author abreslav
*/
public class TypeParameterDescriptor extends NamedAnnotatedImpl {
private final Variance variance;
private final Collection<Type> upperBounds;
private final Set<Type> upperBounds;
private final TypeConstructor typeConstructor;
public TypeParameterDescriptor(List<Attribute> attributes, Variance variance, String name, Collection<Type> upperBounds) {
public TypeParameterDescriptor(List<Attribute> attributes, Variance variance, String name, Set<Type> upperBounds) {
super(attributes, name);
this.variance = variance;
this.upperBounds = upperBounds;
@@ -24,11 +24,15 @@ public class TypeParameterDescriptor extends NamedAnnotatedImpl {
upperBounds);
}
public TypeParameterDescriptor(List<Attribute> attributes, Variance variance, String name) {
this(attributes, variance, name, Collections.singleton(JetStandardClasses.getNullableAnyType()));
}
public Variance getVariance() {
return variance;
}
public Collection<Type> getUpperBounds() {
public Set<Type> getUpperBounds() {
return upperBounds;
}
@@ -1,5 +1,10 @@
package org.jetbrains.jet.lang.types;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* @author abreslav
*/
@@ -10,4 +15,41 @@ public class TypeUtils {
}
return new TypeImpl(type.getAttributes(), type.getConstructor(), true, type.getArguments(), type.getMemberDomain());
}
public static Type intersect(Set<Type> types) {
assert !types.isEmpty();
if (types.size() == 1) {
return types.iterator().next();
}
StringBuilder debugName = new StringBuilder();
boolean nullable = false;
for (Iterator<Type> iterator = types.iterator(); iterator.hasNext();) {
Type type = iterator.next();
nullable |= type.isNullable();
debugName.append(type.toString());
if (iterator.hasNext()) {
debugName.append(" & ");
}
}
List<Attribute> noAttributes = Collections.<Attribute>emptyList();
TypeConstructor constructor = new TypeConstructor(noAttributes, debugName.toString(), Collections.<TypeParameterDescriptor>emptyList(), types);
return new TypeImpl(
noAttributes,
constructor,
nullable,
Collections.<TypeProjection>emptyList(),
JetStandardClasses.STUB);
}
public static Type makeNullableIfNeeded(Type type, boolean nullable) {
if (nullable) {
return makeNullable(type);
}
return type;
}
}