diff --git a/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java b/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java index 5378819456e..a328ead6c8c 100644 --- a/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java +++ b/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java @@ -24,11 +24,20 @@ public class ClassDescriptorResolver { @Nullable public ClassDescriptor resolveClassDescriptor(@NotNull JetScope scope, @NotNull JetClass classElement) { TypeParameterExtensibleScope extensibleScope = new TypeParameterExtensibleScope(scope); + + // This call has side-effects on the extensibleScope (fills it in) + List typeParameters + = resolveTypeParameters(extensibleScope, classElement.getTypeParameters()); + + List delegationSpecifiers = classElement.getDelegationSpecifiers(); + Collection superclasses = delegationSpecifiers.isEmpty() + ? Collections.singleton(JetStandardClasses.getAnyType()) + : resolveTypes(extensibleScope, delegationSpecifiers); return new ClassDescriptor( - AttributeResolver.INSTANCE.resolveAttributes(classElement.getModifierList()), - classElement.getName(), - resolveTypeParameters(extensibleScope, classElement.getTypeParameters()), - resolveTypes(extensibleScope, classElement.getDelegationSpecifiers()) + AttributeResolver.INSTANCE.resolveAttributes(classElement.getModifierList()), + classElement.getName(), + typeParameters, + superclasses ); } diff --git a/idea/src/org/jetbrains/jet/lang/types/JetTypeChecker.java b/idea/src/org/jetbrains/jet/lang/types/JetTypeChecker.java index a4f8ea17062..9cccb7ccfb4 100644 --- a/idea/src/org/jetbrains/jet/lang/types/JetTypeChecker.java +++ b/idea/src/org/jetbrains/jet/lang/types/JetTypeChecker.java @@ -5,6 +5,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.JetNodeTypes; import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.jet.lang.resolve.JetScope; +import org.jetbrains.jet.lang.resolve.TypeResolver; +import org.jetbrains.jet.lexer.JetTokens; import java.util.*; @@ -35,12 +38,12 @@ public class JetTypeChecker { : "namespace" // for the root namespace */ - public Type getType(JetExpression expression) { + public Type getType(@NotNull final JetScope scope, @NotNull JetExpression expression) { final Type[] result = new Type[1]; expression.accept(new JetVisitor() { @Override public void visitParenthesizedExpression(JetParenthesizedExpression expression) { - result[0] = getType(expression.getExpression()); + result[0] = getType(scope, expression.getExpression()); } @Override @@ -97,6 +100,22 @@ public class JetTypeChecker { throw new UnsupportedOperationException("Return some reflection interface"); // TODO } + @Override + public void visitBinaryWithTypeRHSExpression(JetBinaryExpressionWithTypeRHS expression) { + if (expression.getOperationSign() == JetTokens.COLON) { + Type actualType = getType(scope, expression.getLeft()); + Type expectedType = TypeResolver.INSTANCE.resolveType(scope, expression.getRight()); + if (isSubtypeOf(actualType, expectedType)) { + result[0] = expectedType; + return; + } else { + // TODO + throw new UnsupportedOperationException("Type mismatch: expected " + expectedType + " but found " + actualType); + } + } + throw new UnsupportedOperationException(); // TODO + } + @Override public void visitIfExpression(JetIfExpression expression) { // TODO : check condition type @@ -106,8 +125,8 @@ public class JetTypeChecker { // TODO : type-check the branch result[0] = JetStandardClasses.getUnitType(); } else { - Type thenType = getType(expression.getThen()); - Type elseType = getType(elseBranch); + Type thenType = getType(scope, expression.getThen()); + Type elseType = getType(scope, elseBranch); result[0] = commonSupertype(thenType, elseType); } } @@ -117,7 +136,7 @@ public class JetTypeChecker { List entries = expression.getEntries(); List types = new ArrayList(); for (JetExpression entry : entries) { - types.add(getType(entry)); + types.add(getType(scope, entry)); } // TODO : labels result[0] = JetStandardClasses.getTupleType(types); @@ -149,7 +168,13 @@ public class JetTypeChecker { assert !elseOrder.isEmpty() : "No common supertype"; // TODO: support multiple common supertypes - return elseOrder.get(0); + Type result = elseOrder.get(0); + + if (thenType.isNullable() || elseType.isNullable()) { + return TypeUtils.makeNullable(result); + } + assert !result.isNullable(); + return result; } private void topologicallySort(Type current, Map visited, List topologicalOrder, @Nullable Map filter) { @@ -165,11 +190,11 @@ public class JetTypeChecker { assert equalTypes(visited.get(supertypeConstructor), supertype); continue; } - Type substitutedSupertype = substitute(substitutionContext, supertype); + Type substitutedSupertype = substituteInType(substitutionContext, supertype); topologicallySort(substitutedSupertype, visited, topologicalOrder, filter); } if (filter != null && filter.containsKey(current.getConstructor())) { - assert equalTypes(filter.get(current.getConstructor()), current); + assert equalTypes(filter.get(current.getConstructor()), current) : filter.get(current.getConstructor()) + " != " + current; topologicalOrder.add(0, current); } } @@ -204,7 +229,7 @@ public class JetTypeChecker { for (Type immediateSupertype : constructor.getSupertypes()) { Type correspondingSupertype = findCorrespondingSupertype(immediateSupertype, supertype); if (correspondingSupertype != null) { - return substitute(getSubstitutionContext(subtype), correspondingSupertype); + return substituteInType(getSubstitutionContext(subtype), correspondingSupertype); } } return null; @@ -223,17 +248,34 @@ public class JetTypeChecker { return parameterValues; } + private Type substituteInType(Map substitutionContext, Type type) { + TypeProjection value = substitutionContext.get(type.getConstructor()); + assert value == null : "For now this is used only for supertypes, thus no variables"; + + return specializeType(type, substituteInArguments(substitutionContext, type)); + } + @NotNull - private Type substitute(Map parameterValues, Type subject) { - TypeProjection value = parameterValues.get(subject.getConstructor()); + private TypeProjection substitute(Map 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.getType(); + return value; } + List newArguments = substituteInArguments(parameterValues, subjectType); + return new TypeProjection(projectionKind, specializeType(subjectType, newArguments)); + } + + private List substituteInArguments(Map parameterValues, Type subjectType) { List newArguments = new ArrayList(); - for (TypeProjection argument : subject.getArguments()) { - newArguments.add(new TypeProjection(argument.getProjectionKind(), substitute(parameterValues, argument.getType()))); + for (TypeProjection argument : subjectType.getArguments()) { + newArguments.add(substitute(parameterValues, argument)); } - return specializeType(subject, newArguments); + return newArguments; } private Type specializeType(Type type, List newArguments) { @@ -315,7 +357,7 @@ public class JetTypeChecker { return true; } - public boolean equalTypes(Type type1, Type type2) { + public boolean equalTypes(@NotNull Type type1, @NotNull Type type2) { if (!type1.getConstructor().equals(type2.getConstructor())) { return false; } @@ -330,8 +372,10 @@ public class JetTypeChecker { if (typeProjection1.getProjectionKind() != typeProjection2.getProjectionKind()) { return false; } - if (!equalTypes(typeProjection1.getType(), typeProjection2.getType())) { - 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/TypeProjection.java b/idea/src/org/jetbrains/jet/lang/types/TypeProjection.java index c505da0d357..b8887ce9707 100644 --- a/idea/src/org/jetbrains/jet/lang/types/TypeProjection.java +++ b/idea/src/org/jetbrains/jet/lang/types/TypeProjection.java @@ -26,6 +26,12 @@ public class TypeProjection { @Override public String toString() { + if (projection == ProjectionKind.NEITHER_OUT_NOR_IN) { + return projection.toString(); + } + if (projection == ProjectionKind.NO_PROJECTION) { + return type + ""; + } return projection + " " + type; } } diff --git a/idea/src/org/jetbrains/jet/lang/types/TypeUtils.java b/idea/src/org/jetbrains/jet/lang/types/TypeUtils.java new file mode 100644 index 00000000000..586a8964718 --- /dev/null +++ b/idea/src/org/jetbrains/jet/lang/types/TypeUtils.java @@ -0,0 +1,13 @@ +package org.jetbrains.jet.lang.types; + +/** + * @author abreslav + */ +public class TypeUtils { + public static Type makeNullable(Type type) { + if (type.isNullable()) { + return type; + } + return new TypeImpl(type.getAttributes(), type.getConstructor(), true, type.getArguments(), type.getMemberDomain()); + } +} diff --git a/idea/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java b/idea/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java index ef4762cc4ed..490bcab6ab2 100644 --- a/idea/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java +++ b/idea/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java @@ -77,11 +77,27 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase { } public void testIf() throws Exception { - assertType("if (true) 1", JetStandardClasses.getUnitType()); - assertType("if (true) 1 else 1", JetStandardClasses.getIntType()); - assertType("if (true) 1 else return", JetStandardClasses.getIntType()); - assertType("if (true) return else 1", JetStandardClasses.getIntType()); - assertType("if (true) return else return", JetStandardClasses.getNothingType()); + assertType("if (true) 1", "Unit"); + assertType("if (true) 1 else 1", "Int"); + assertType("if (true) 1 else return", "Int"); + assertType("if (true) return else 1", "Int"); + assertType("if (true) return else return", "Nothing"); + + assertType("if (true) 1 else null", "Int?"); + assertType("if (true) null else null", "Nothing?"); + + assertType("if (true) 1 else '1'", "Any"); + + assertType("if (true) null : Base_T<*>? else null : Derived_T<*>?", "Base_T<*>?"); + assertType("if (true) null : Base_inT<*>? else null : Derived_T<*>?", "Any?"); + assertType("if (true) null : DDerived_T? else null : Derived_T?", "Derived_T?"); + assertType("if (true) null : DDerived_T? else null : DDerived1_T?", "Derived_T?"); + + assertType("if (true) null : Base_T? else null : Base_T?", "Any?"); + assertType("if (true) null : Base_T? else null : Base_T?", "Base_T?"); + assertType("if (true) null : Derived_T? else null : Base_T?", "Base_T?"); + assertType("if (true) null : Derived_T? else null : Base_T?", "Any?"); + assertType("if (true) null : Base_T? else null : Base_T<*>?", "Base_T<*>?"); } public void testBasicSubtyping() throws Exception { @@ -187,6 +203,7 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase { assertSubtype("Derived_T", "Base_T"); assertSubtype("Derived_outT", "Base_outT"); assertSubtype("Derived_inT", "Base_inT"); + assertSubtype("Derived_T<*>", "Base_T<*>"); assertNotSubtype("Derived_T", "Base_T"); @@ -212,6 +229,9 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase { assertSubtype("Nothing", "Int"); assertSubtype("Nothing?", "Int?"); assertNotSubtype("Nothing?", "Int"); + + assertSubtype("Nothing?", "Base_T<*>?"); + assertSubtype("Nothing?", "Derived_T<*>?"); } public void testImplicitConversions() throws Exception { @@ -227,8 +247,8 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase { } private static void assertSubtypingRelation(String type1, String type2, boolean expected) { - Type typeNode1 = TypeResolver.INSTANCE.resolveType(ClassDefinitions.BASIC_SCOPE, JetChangeUtil.createType(getProject(), type1)); - Type typeNode2 = TypeResolver.INSTANCE.resolveType(ClassDefinitions.BASIC_SCOPE, JetChangeUtil.createType(getProject(), type2)); + Type typeNode1 = makeType(type1); + Type typeNode2 = makeType(type2); boolean result = JetTypeChecker.INSTANCE.isSubtypeOf( typeNode1, typeNode2); @@ -253,15 +273,29 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase { private static void assertType(String expression, Type expectedType) { Project project = getProject(); JetExpression jetExpression = JetChangeUtil.createExpression(project, expression); - Type type = JetTypeChecker.INSTANCE.getType(jetExpression); + Type type = JetTypeChecker.INSTANCE.getType(ClassDefinitions.BASIC_SCOPE, jetExpression); assertTrue(type + "!=" + expectedType, JetTypeChecker.INSTANCE.equalTypes(type, expectedType)); } + private static void assertType(String expression, String expectedTypeStr) { + Project project = getProject(); + 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)); + } + + private static Type makeType(String typeStr) { + return TypeResolver.INSTANCE.resolveType(ClassDefinitions.BASIC_SCOPE, JetChangeUtil.createType(getProject(), typeStr)); + } + private static class ClassDefinitions { private static Map CLASSES = new HashMap(); private static String[] CLASS_DECLARATIONS = { "class Base_T", "class Derived_T : Base_T", + "class DDerived_T : Derived_T", + "class DDerived1_T : Derived_T", "class Base_inT", "class Derived_inT : Base_inT", "class Base_outT",