001 /*
002 * Copyright 2010-2015 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package org.jetbrains.kotlin.types;
018
019 import com.google.common.base.Predicate;
020 import com.google.common.collect.Maps;
021 import com.google.common.collect.Sets;
022 import org.jetbrains.annotations.NotNull;
023
024 import java.util.List;
025 import java.util.Map;
026 import java.util.Set;
027
028 public class TypeUnifier {
029
030 public interface UnificationResult {
031 boolean isSuccess();
032
033 @NotNull
034 Map<TypeConstructor, TypeProjection> getSubstitution();
035 }
036
037 /**
038 * Finds a substitution S that turns {@code projectWithVariables} to {@code knownProjection}.
039 *
040 * Example:
041 * known = List<String>
042 * withVariables = List<X>
043 * variables = {X}
044 *
045 * result = X -> String
046 *
047 * Only types accepted by {@code isVariable} are considered variables.
048 */
049 @NotNull
050 public static UnificationResult unify(
051 @NotNull TypeProjection knownProjection,
052 @NotNull TypeProjection projectWithVariables,
053 @NotNull Predicate<TypeConstructor> isVariable
054 ) {
055 UnificationResultImpl result = new UnificationResultImpl();
056 doUnify(knownProjection, projectWithVariables, isVariable, result);
057 return result;
058 }
059
060 private static void doUnify(
061 TypeProjection knownProjection,
062 TypeProjection projectWithVariables,
063 Predicate<TypeConstructor> isVariable,
064 UnificationResultImpl result
065 ) {
066 KotlinType known = knownProjection.getType();
067 KotlinType withVariables = projectWithVariables.getType();
068
069 // in Foo ~ in X => Foo ~ X
070 Variance knownProjectionKind = knownProjection.getProjectionKind();
071 Variance withVariablesProjectionKind = projectWithVariables.getProjectionKind();
072 if (knownProjectionKind == withVariablesProjectionKind && knownProjectionKind != Variance.INVARIANT) {
073 doUnify(new TypeProjectionImpl(known), new TypeProjectionImpl(withVariables), isVariable, result);
074 return;
075 }
076
077 // Foo? ~ X? => Foo ~ X
078 if (known.isMarkedNullable() && withVariables.isMarkedNullable()) {
079 doUnify(
080 new TypeProjectionImpl(knownProjectionKind, TypeUtils.makeNotNullable(known)),
081 new TypeProjectionImpl(withVariablesProjectionKind, TypeUtils.makeNotNullable(withVariables)),
082 isVariable,
083 result
084 );
085 return;
086 }
087
088 // in Foo ~ out X => fail
089 // in Foo ~ X => may be OK
090 if (knownProjectionKind != withVariablesProjectionKind && withVariablesProjectionKind != Variance.INVARIANT) {
091 result.fail();
092 return;
093 }
094
095 // Foo ~ X? => fail
096 if (!known.isMarkedNullable() && withVariables.isMarkedNullable()) {
097 result.fail();
098 return;
099 }
100
101 // Foo ~ X => x |-> Foo
102 TypeConstructor maybeVariable = withVariables.getConstructor();
103 if (isVariable.apply(maybeVariable)) {
104 result.put(maybeVariable, new TypeProjectionImpl(knownProjectionKind, known));
105 return;
106 }
107
108 // Foo? ~ Foo || in Foo ~ Foo || Foo ~ Bar
109 boolean structuralMismatch = known.isMarkedNullable() != withVariables.isMarkedNullable()
110 || knownProjectionKind != withVariablesProjectionKind
111 || !known.getConstructor().equals(withVariables.getConstructor());
112 if (structuralMismatch) {
113 result.fail();
114 return;
115 }
116
117 // Foo<A> ~ Foo<B, C>
118 if (known.getArguments().size() != withVariables.getArguments().size()) {
119 result.fail();
120 return;
121 }
122
123 // Foo ~ Foo
124 if (known.getArguments().isEmpty()) {
125 return;
126 }
127
128 // Foo<...> ~ Foo<...>
129 List<TypeProjection> knownArguments = known.getArguments();
130 List<TypeProjection> withVariablesArguments = withVariables.getArguments();
131 for (int i = 0; i < knownArguments.size(); i++) {
132 TypeProjection knownArg = knownArguments.get(i);
133 TypeProjection withVariablesArg = withVariablesArguments.get(i);
134
135 doUnify(knownArg, withVariablesArg, isVariable, result);
136 }
137 }
138
139 private static class UnificationResultImpl implements UnificationResult {
140 private boolean success = true;
141 private final Map<TypeConstructor, TypeProjection> substitution = Maps.newHashMapWithExpectedSize(1);
142 private final Set<TypeConstructor> failedVariables = Sets.newHashSetWithExpectedSize(0);
143
144 @Override
145 public boolean isSuccess() {
146 return success;
147 }
148
149 public void fail() {
150 success = false;
151 }
152
153 @Override
154 @NotNull
155 public Map<TypeConstructor, TypeProjection> getSubstitution() {
156 return substitution;
157 }
158
159 public void put(TypeConstructor key, TypeProjection value) {
160 if (failedVariables.contains(key)) return;
161
162 TypeProjection oldValue = substitution.put(key, value);
163 if (oldValue != null && !oldValue.equals(value)) {
164 substitution.remove(key);
165 failedVariables.add(key);
166 fail();
167 }
168 }
169 }
170 }