diff --git a/idea/src/org/jetbrains/jet/lang/resolve/TypeResolver.java b/idea/src/org/jetbrains/jet/lang/resolve/TypeResolver.java index 231c8a88411..9117b284d8d 100644 --- a/idea/src/org/jetbrains/jet/lang/resolve/TypeResolver.java +++ b/idea/src/org/jetbrains/jet/lang/resolve/TypeResolver.java @@ -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 resolveTypeProjections(JetScope scope, List argumentElements) { + private List resolveTypeProjections(JetScope scope, TypeConstructor constructor, List argumentElements) { final List arguments = new ArrayList(); - 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 upperBounds = constructor.getParameters().get(i).getUpperBounds(); + arguments.add(new TypeProjection(ProjectionKind.OUT_ONLY, TypeUtils.intersect(upperBounds))); + } + else { + // TODO : handle the Foo case + type = resolveType(scope, argumentElement.getTypeReference()); + arguments.add(new TypeProjection(projectionKind, type)); } - TypeProjection typeProjection = new TypeProjection(projectionKind, type); - arguments.add(typeProjection); } return arguments; } diff --git a/idea/src/org/jetbrains/jet/lang/types/DfsNodeHandler.java b/idea/src/org/jetbrains/jet/lang/types/DfsNodeHandler.java new file mode 100644 index 00000000000..8b47b363ffb --- /dev/null +++ b/idea/src/org/jetbrains/jet/lang/types/DfsNodeHandler.java @@ -0,0 +1,19 @@ +package org.jetbrains.jet.lang.types; + +/** + * @author abreslav + */ +public class DfsNodeHandler { + + public void beforeChildren(Type current) { + + } + + public void afterChildren(Type current) { + + } + + public R result() { + return null; + } +} diff --git a/idea/src/org/jetbrains/jet/lang/types/JetStandardClasses.java b/idea/src/org/jetbrains/jet/lang/types/JetStandardClasses.java index 49af3c11ba9..77b6e06829e 100644 --- a/idea/src/org/jetbrains/jet/lang/types/JetStandardClasses.java +++ b/idea/src/org/jetbrains/jet/lang/types/JetStandardClasses.java @@ -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; diff --git a/idea/src/org/jetbrains/jet/lang/types/JetTypeChecker.java b/idea/src/org/jetbrains/jet/lang/types/JetTypeChecker.java index 9cccb7ccfb4..c9d8857543d 100644 --- a/idea/src/org/jetbrains/jet/lang/types/JetTypeChecker.java +++ b/idea/src/org/jetbrains/jet/lang/types/JetTypeChecker.java @@ -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 types) { + Collection typeSet = new HashSet(types); + assert !typeSet.isEmpty(); + boolean nullable = false; + for (Iterator iterator = typeSet.iterator(); iterator.hasNext();) { + Type type = iterator.next(); + if (JetStandardClasses.isNothing(type)) { + iterator.remove(); + } + nullable |= type.isNullable(); } - List thenOrder = new LinkedList(); // adding to the beginning - Map visited = new HashMap(); - topologicallySort(thenType, visited, thenOrder, null); - - List elseOrder = new LinkedList(); // adding to the beginning - topologicallySort(elseType, new HashMap(), 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> commonSupertypes = computeCommonRawSupertypes(typeSet); + while (commonSupertypes.size() > 1) { + HashSet merge = new HashSet(); + for (Set supertypes : commonSupertypes.values()) { + merge.addAll(supertypes); + } + commonSupertypes = computeCommonRawSupertypes(merge); + } + assert !commonSupertypes.isEmpty(); + Map.Entry> entry = commonSupertypes.entrySet().iterator().next(); + Type result = computeSupertypeProjections(entry.getKey(), entry.getValue()); + + return TypeUtils.makeNullableIfNeeded(result, nullable); + } + + private Type computeSupertypeProjections(TypeConstructor constructor, Set 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 parameters = constructor.getParameters(); + List newProjections = new ArrayList(); + for (int i = 0, parametersSize = parameters.size(); i < parametersSize; i++) { + TypeParameterDescriptor parameterDescriptor = parameters.get(i); + Set typeProjections = new HashSet(); + 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.emptyList(), constructor, nullable, newProjections, JetStandardClasses.STUB); + } + + private TypeProjection computeSupertypeProjection(TypeParameterDescriptor parameterDescriptor, Set typeProjections) { + // TODO: equals/hashCode for projections + if (typeProjections.size() == 1) { + return typeProjections.iterator().next(); + } + + Set ins = new HashSet(); + Set outs = new HashSet(); + + 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> computeCommonRawSupertypes(Collection types) { + assert !types.isEmpty(); + + final Map> constructorToAllInstances = new HashMap>(); + Set commonSuperclasses = null; + + List order = null; + for (Iterator iterator = types.iterator(); iterator.hasNext();) { + Type type = iterator.next(); + + Set visited = new HashSet(); + + order = dfs(type, visited, new DfsNodeHandler>() { + public LinkedList list = new LinkedList(); + + @Override + public void beforeChildren(Type current) { + TypeConstructor constructor = current.getConstructor(); + + Set instances = constructorToAllInstances.get(constructor); + if (instances == null) { + instances = new HashSet(); + constructorToAllInstances.put(constructor, instances); + } + instances.add(current); + } + + @Override + public void afterChildren(Type current) { + list.addFirst(current.getConstructor()); + } + + @Override + public List result() { + return list; + } + }); + + if (commonSuperclasses == null) { + commonSuperclasses = visited; + } else { + commonSuperclasses.retainAll(visited); + } + } + assert order != null; + + Set notSource = new HashSet(); + Map> result = new HashMap>(); + 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 visited, List topologicalOrder, @Nullable Map filter) { - Type visitedOccurrence = visited.put(current.getConstructor(), current); - if (visitedOccurrence != null) { - assert equalTypes(visitedOccurrence, current); + private void markAll(TypeConstructor typeConstructor, Set markerSet) { + markerSet.add(typeConstructor); + for (Type type : typeConstructor.getSupertypes()) { + markAll(type.getConstructor(), markerSet); + } + } + + private R dfs(Type current, Set visited, DfsNodeHandler handler) { + doDfs(current, visited, handler); + return handler.result(); + } + + private void doDfs(Type current, Set visited, DfsNodeHandler handler) { + if (!visited.add(current.getConstructor())) { return; } + handler.beforeChildren(current); Map 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 type1Arguments = type1.getArguments(); - List 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); } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/lang/types/Type.java b/idea/src/org/jetbrains/jet/lang/types/Type.java index 0bc961dc3f9..a8af560c0f2 100644 --- a/idea/src/org/jetbrains/jet/lang/types/Type.java +++ b/idea/src/org/jetbrains/jet/lang/types/Type.java @@ -13,6 +13,4 @@ public interface Type extends Annotated { boolean isNullable(); @NotNull TypeMemberDomain getMemberDomain(); - - @NotNull Type getNullableVersion(); } diff --git a/idea/src/org/jetbrains/jet/lang/types/TypeImpl.java b/idea/src/org/jetbrains/jet/lang/types/TypeImpl.java index 5969d954bd1..16ad5b5520e 100644 --- a/idea/src/org/jetbrains/jet/lang/types/TypeImpl.java +++ b/idea/src/org/jetbrains/jet/lang/types/TypeImpl.java @@ -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.emptyList(), constructor, false, Collections.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 type1Arguments = type1.getArguments(); + List 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; + } + + } diff --git a/idea/src/org/jetbrains/jet/lang/types/TypeParameterDescriptor.java b/idea/src/org/jetbrains/jet/lang/types/TypeParameterDescriptor.java index 9c722456bde..6c304fd48c6 100644 --- a/idea/src/org/jetbrains/jet/lang/types/TypeParameterDescriptor.java +++ b/idea/src/org/jetbrains/jet/lang/types/TypeParameterDescriptor.java @@ -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 upperBounds; + private final Set upperBounds; private final TypeConstructor typeConstructor; - public TypeParameterDescriptor(List attributes, Variance variance, String name, Collection upperBounds) { + public TypeParameterDescriptor(List attributes, Variance variance, String name, Set upperBounds) { super(attributes, name); this.variance = variance; this.upperBounds = upperBounds; @@ -24,11 +24,15 @@ public class TypeParameterDescriptor extends NamedAnnotatedImpl { upperBounds); } + public TypeParameterDescriptor(List attributes, Variance variance, String name) { + this(attributes, variance, name, Collections.singleton(JetStandardClasses.getNullableAnyType())); + } + public Variance getVariance() { return variance; } - public Collection getUpperBounds() { + public Set getUpperBounds() { return upperBounds; } diff --git a/idea/src/org/jetbrains/jet/lang/types/TypeUtils.java b/idea/src/org/jetbrains/jet/lang/types/TypeUtils.java index 586a8964718..c0d10921199 100644 --- a/idea/src/org/jetbrains/jet/lang/types/TypeUtils.java +++ b/idea/src/org/jetbrains/jet/lang/types/TypeUtils.java @@ -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 types) { + assert !types.isEmpty(); + + if (types.size() == 1) { + return types.iterator().next(); + } + + StringBuilder debugName = new StringBuilder(); + boolean nullable = false; + for (Iterator iterator = types.iterator(); iterator.hasNext();) { + Type type = iterator.next(); + + nullable |= type.isNullable(); + + debugName.append(type.toString()); + if (iterator.hasNext()) { + debugName.append(" & "); + } + } + + List noAttributes = Collections.emptyList(); + TypeConstructor constructor = new TypeConstructor(noAttributes, debugName.toString(), Collections.emptyList(), types); + return new TypeImpl( + noAttributes, + constructor, + nullable, + Collections.emptyList(), + JetStandardClasses.STUB); + } + + public static Type makeNullableIfNeeded(Type type, boolean nullable) { + if (nullable) { + return makeNullable(type); + } + return type; + } }