diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/DescriptorUtils.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/DescriptorUtils.java index c3295dbb0d6..2413cd5bd85 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/DescriptorUtils.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/DescriptorUtils.java @@ -17,7 +17,6 @@ package org.jetbrains.jet.lang.resolve; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.descriptors.*; @@ -29,7 +28,9 @@ import org.jetbrains.jet.lang.resolve.name.FqName; import org.jetbrains.jet.lang.resolve.name.FqNameUnsafe; import org.jetbrains.jet.lang.resolve.name.Name; import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor; -import org.jetbrains.jet.lang.types.*; +import org.jetbrains.jet.lang.types.DescriptorSubstitutor; +import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.lang.types.TypeUtils; import org.jetbrains.jet.lang.types.checker.JetTypeChecker; import org.jetbrains.jet.lang.types.lang.JetStandardClasses; import org.jetbrains.jet.lang.types.lang.JetStandardLibrary; @@ -44,58 +45,16 @@ import static org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor public class DescriptorUtils { @NotNull - public static D substituteBounds(@NotNull D functionDescriptor) { + public static D substituteBounds(@NotNull final D functionDescriptor) { final List typeParameters = functionDescriptor.getTypeParameters(); if (typeParameters.isEmpty()) return functionDescriptor; - final Map typeConstructors = Maps.newHashMap(); - for (TypeParameterDescriptor typeParameter : typeParameters) { - typeConstructors.put(typeParameter.getTypeConstructor(), typeParameter); - } - //noinspection unchecked - return (D) functionDescriptor.substitute(new TypeSubstitutor(TypeSubstitution.EMPTY) { - @Override - public boolean inRange(@NotNull TypeConstructor typeConstructor) { - return typeConstructors.containsKey(typeConstructor); - } - @Override - public boolean isEmpty() { - return typeParameters.isEmpty(); - } + // TODO: this does not handle any recursion in the bounds + @SuppressWarnings("unchecked") + D substitutedFunction = (D) functionDescriptor.substitute(DescriptorSubstitutor.createUpperBoundsSubstitutor(typeParameters)); + assert substitutedFunction != null : "Substituting upper bounds should always be legal"; - @NotNull - @Override - public TypeSubstitution getSubstitution() { - throw new UnsupportedOperationException(); - } - - @NotNull - @Override - public JetType safeSubstitute(@NotNull JetType type, @NotNull Variance howThisTypeIsUsed) { - JetType substituted = substitute(type, howThisTypeIsUsed); - if (substituted == null) { - return ErrorUtils.createErrorType("Substitution failed"); - } - return substituted; - } - - @Nullable - @Override - public JetType substitute(@NotNull JetType type, @NotNull Variance howThisTypeIsUsed) { - TypeParameterDescriptor typeParameterDescriptor = typeConstructors.get(type.getConstructor()); - if (typeParameterDescriptor != null) { - switch (howThisTypeIsUsed) { - case INVARIANT: - return type; - case IN_VARIANCE: - throw new UnsupportedOperationException(); // TODO : lower bounds - case OUT_VARIANCE: - return typeParameterDescriptor.getDefaultType(); - } - } - return super.substitute(type, howThisTypeIsUsed); - } - }); + return substitutedFunction; } public static Modality convertModality(Modality modality, boolean makeNonAbstract) { diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/types/DescriptorSubstitutor.java b/compiler/frontend/src/org/jetbrains/jet/lang/types/DescriptorSubstitutor.java index 132f75ede6e..eb579e2338b 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/types/DescriptorSubstitutor.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/types/DescriptorSubstitutor.java @@ -16,12 +16,17 @@ package org.jetbrains.jet.lang.types; +import com.google.common.base.Function; +import com.google.common.collect.Collections2; import com.google.common.collect.Maps; import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.descriptors.ClassifierDescriptor; import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor; import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptorImpl; +import org.jetbrains.jet.utils.DFS; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -30,6 +35,13 @@ import java.util.Map; */ public class DescriptorSubstitutor { + private static final Function PROJECTIONS_TO_TYPES = new Function() { + @Override + public JetType apply(TypeProjection projection) { + return projection.getType(); + } + }; + @NotNull public static TypeSubstitutor substituteTypeParameters( @NotNull List typeParameters, @@ -80,4 +92,69 @@ public class DescriptorSubstitutor { return substitutor; } + + + @NotNull + public static TypeSubstitutor createUpperBoundsSubstitutor( + @NotNull final List typeParameters + ) { + Map mutableSubstitution = Maps.newHashMap(); + TypeSubstitutor substitutor = TypeSubstitutor.create(mutableSubstitution); + + // todo assert: no loops + for (TypeParameterDescriptor descriptor : topologicallySortTypeParameters(typeParameters)) { + JetType upperBoundsAsType = descriptor.getUpperBoundsAsType(); + JetType substitutedUpperBoundsAsType = substitutor.substitute(upperBoundsAsType, Variance.INVARIANT); + mutableSubstitution.put(descriptor.getTypeConstructor(), new TypeProjection(substitutedUpperBoundsAsType)); + } + + return substitutor; + } + + private static List topologicallySortTypeParameters(final List typeParameters) { + // In the end, we want every parameter to have no references to those after it in the list + // This gives us the reversed order: the one that refers to everybody else comes first + List topOrder = DFS.topologicalOrder( + typeParameters, + new DFS.Neighbors() { + @NotNull + @Override + public Iterable getNeighbors(TypeParameterDescriptor current) { + return getTypeParametersFromUpperBounds(current, typeParameters); + } + }); + + assert topOrder.size() == typeParameters.size() : "All type parameters must be visited, but only " + topOrder + " were"; + + // Now, the one that refers to everybody else stands in the last position + Collections.reverse(topOrder); + return topOrder; + } + + private static List getTypeParametersFromUpperBounds( + TypeParameterDescriptor current, + final List typeParameters + ) { + return DFS.dfs( + current.getUpperBounds(), + new DFS.Neighbors() { + @NotNull + @Override + public Iterable getNeighbors(JetType current) { + return Collections2.transform(current.getArguments(), PROJECTIONS_TO_TYPES); + } + }, + new DFS.NodeHandlerWithListResult() { + @Override + public void beforeChildren(JetType current) { + ClassifierDescriptor declarationDescriptor = current.getConstructor().getDeclarationDescriptor(); + // typeParameters in a list, but it contains very few elements, so it's fine to call contains() on it + //noinspection SuspiciousMethodCalls + if (typeParameters.contains(declarationDescriptor)) { + result.add((TypeParameterDescriptor) declarationDescriptor); + } + } + } + ); + } } diff --git a/compiler/testData/diagnostics/tests/substitutions/upperBoundsSubstitutionForOverloadResolution.kt b/compiler/testData/diagnostics/tests/substitutions/upperBoundsSubstitutionForOverloadResolution.kt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/testData/diagnostics/tests/substitutions/upperBoundsSubstitutionForOverloadResolutionWithAmbiguity.kt b/compiler/testData/diagnostics/tests/substitutions/upperBoundsSubstitutionForOverloadResolutionWithAmbiguity.kt new file mode 100644 index 00000000000..eb116a27a17 --- /dev/null +++ b/compiler/testData/diagnostics/tests/substitutions/upperBoundsSubstitutionForOverloadResolutionWithAmbiguity.kt @@ -0,0 +1,14 @@ +// FILE: foo.kt +package foo + +fun f(l: List) {} + +// FILE: main.kt + +import foo.* + +fun f(l: List) {} + +fun test(l: List) { + f(l) +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/substitutions/upperBoundsSubstitutionForOverloadResolutionWithErrorTypes.kt b/compiler/testData/diagnostics/tests/substitutions/upperBoundsSubstitutionForOverloadResolutionWithErrorTypes.kt new file mode 100644 index 00000000000..765c1e0ae93 --- /dev/null +++ b/compiler/testData/diagnostics/tests/substitutions/upperBoundsSubstitutionForOverloadResolutionWithErrorTypes.kt @@ -0,0 +1,7 @@ +fun f1(l: List1): T {throw Exception()} // ERROR type here +fun f1(l: List2): T {throw Exception()} // ERROR type here +fun f1(c: Collection): T{throw Exception()} + +fun test(l: List) { + f1(l) +} \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java index a01e4041bbe..2453b1f30b4 100644 --- a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java @@ -3210,6 +3210,16 @@ public class JetDiagnosticsTestGenerated extends AbstractDiagnosticsTestWithEage doTest("compiler/testData/diagnostics/tests/substitutions/kt1558-short.kt"); } + @TestMetadata("upperBoundsSubstitutionForOverloadResolutionWithAmbiguity.kt") + public void testUpperBoundsSubstitutionForOverloadResolutionWithAmbiguity() throws Exception { + doTest("compiler/testData/diagnostics/tests/substitutions/upperBoundsSubstitutionForOverloadResolutionWithAmbiguity.kt"); + } + + @TestMetadata("upperBoundsSubstitutionForOverloadResolutionWithErrorTypes.kt") + public void testUpperBoundsSubstitutionForOverloadResolutionWithErrorTypes() throws Exception { + doTest("compiler/testData/diagnostics/tests/substitutions/upperBoundsSubstitutionForOverloadResolutionWithErrorTypes.kt"); + } + } @TestMetadata("compiler/testData/diagnostics/tests/subtyping") diff --git a/compiler/tests/org/jetbrains/jet/types/BoundsSubstitutorTest.java b/compiler/tests/org/jetbrains/jet/types/BoundsSubstitutorTest.java new file mode 100644 index 00000000000..5af7387d880 --- /dev/null +++ b/compiler/tests/org/jetbrains/jet/types/BoundsSubstitutorTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2010-2012 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.types; + +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.jet.ConfigurationKind; +import org.jetbrains.jet.cli.jvm.compiler.JetCoreEnvironment; +import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; +import org.jetbrains.jet.lang.descriptors.ModuleDescriptor; +import org.jetbrains.jet.lang.psi.JetFile; +import org.jetbrains.jet.lang.psi.JetPsiFactory; +import org.jetbrains.jet.lang.resolve.DescriptorUtils; +import org.jetbrains.jet.lang.resolve.lazy.KotlinTestWithEnvironment; +import org.jetbrains.jet.lang.resolve.lazy.LazyResolveTestUtil; +import org.jetbrains.jet.lang.resolve.name.Name; +import org.jetbrains.jet.resolve.DescriptorRenderer; + +import java.util.Collection; +import java.util.Collections; + +/** + * @author abreslav + */ +public class BoundsSubstitutorTest extends KotlinTestWithEnvironment { + @Override + protected JetCoreEnvironment createEnvironment() { + return createEnvironmentWithMockJdk(ConfigurationKind.JDK_ONLY); + } + + public void testSimpleSubstitution() throws Exception { + doTest("fun f(l: List): T", + "fun f(l : jet.List) : jet.Any?"); + } + + public void testParameterInBound() throws Exception { + doTest("fun > f(l: List): R", + "fun > f(l : jet.List>) : jet.List"); + } + + public void testWithWhere() throws Exception { + doTest("fun f(l: List): T where T : Any", + "fun f(l : jet.List) : jet.Any"); + } + + public void testWithWhereTwoBounds() throws Exception { + doTest("fun f(l: List): R where T : List, R : Any", + "fun , R : jet.Any> f(l : jet.List) : jet.Any"); + } + + public void testWithWhereTwoBoundsReversed() throws Exception { + doTest("fun f(l: List): R where T : Any, R : List", + "fun > f(l : jet.List>) : jet.List"); + } + + public void testWithWhereTwoBoundsLoop() throws Exception { + doTest("fun f(l: List): R where T : R, R : T", + ""); + } + + private void doTest(String text, String expected) { + JetFile jetFile = JetPsiFactory.createFile(getProject(), "fun.kt", text); + ModuleDescriptor module = LazyResolveTestUtil.resolveLazily(Collections.singletonList(jetFile), getEnvironment()); + Collection functions = module.getRootNamespace().getMemberScope().getFunctions(Name.identifier("f")); + assert functions.size() == 1 : "Many functions defined"; + FunctionDescriptor function = ContainerUtil.getFirstItem(functions); + + FunctionDescriptor substituted = DescriptorUtils.substituteBounds(function); + String actual = DescriptorRenderer.COMPACT.render(substituted); + assertEquals(expected, actual); + } +}