001    /*
002     * Copyright 2010-2013 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.jet.lang.resolve.calls.inference;
018    
019    import com.google.common.collect.Lists;
020    import com.google.common.collect.Maps;
021    import com.google.common.collect.Sets;
022    import com.intellij.openapi.util.Pair;
023    import com.intellij.util.containers.ContainerUtil;
024    import org.jetbrains.annotations.NotNull;
025    import org.jetbrains.annotations.Nullable;
026    import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor;
027    import org.jetbrains.jet.lang.types.*;
028    import org.jetbrains.jet.lang.types.checker.JetTypeChecker;
029    
030    import java.util.*;
031    
032    public class ConstraintsUtil {
033    
034        @NotNull
035        public static Set<JetType> getValues(@Nullable TypeConstraints typeConstraints) {
036            Set<JetType> values = Sets.newLinkedHashSet();
037            if (typeConstraints == null || typeConstraints.isEmpty()) {
038                return values;
039            }
040            TypeConstraints typeConstraintsWithoutErrorTypes = filterNotContainingErrorType(typeConstraints, values);
041            Collection<JetType> exactBounds = typeConstraintsWithoutErrorTypes.getExactBounds();
042            if (exactBounds.size() == 1) {
043                JetType exactBound = exactBounds.iterator().next();
044                if (trySuggestion(exactBound, typeConstraints)) {
045                    return Collections.singleton(exactBound);
046                }
047            }
048            values.addAll(exactBounds);
049    
050            Pair<Collection<JetType>, Collection<JetType>> pair =
051                    TypeUtils.filterNumberTypes(typeConstraintsWithoutErrorTypes.getLowerBounds());
052            Collection<JetType> generalLowerBounds = pair.getFirst();
053            Collection<JetType> numberLowerBounds = pair.getSecond();
054    
055            JetType superTypeOfLowerBounds = commonSupertype(generalLowerBounds);
056            if (trySuggestion(superTypeOfLowerBounds, typeConstraints)) {
057                return Collections.singleton(superTypeOfLowerBounds);
058            }
059            ContainerUtil.addIfNotNull(superTypeOfLowerBounds, values);
060    
061            Collection<JetType> upperBounds = typeConstraintsWithoutErrorTypes.getUpperBounds();
062            for (JetType upperBound : upperBounds) {
063                if (trySuggestion(upperBound, typeConstraints)) {
064                    return Collections.singleton(upperBound);
065                }
066            }
067            //todo
068            //fun <T> foo(t: T, consumer: Consumer<T>): T
069            //foo(1, c: Consumer<Any>) - infer Int, not Any here
070    
071            values.addAll(typeConstraintsWithoutErrorTypes.getUpperBounds());
072    
073            JetType superTypeOfNumberLowerBounds = commonSupertypeForNumberTypes(numberLowerBounds);
074            if (trySuggestion(superTypeOfNumberLowerBounds, typeConstraints)) {
075                return Collections.singleton(superTypeOfNumberLowerBounds);
076            }
077            ContainerUtil.addIfNotNull(superTypeOfNumberLowerBounds, values);
078    
079            if (superTypeOfLowerBounds != null && superTypeOfNumberLowerBounds != null) {
080                JetType superTypeOfAllLowerBounds = commonSupertype(Lists.newArrayList(superTypeOfLowerBounds, superTypeOfNumberLowerBounds));
081                if (trySuggestion(superTypeOfAllLowerBounds, typeConstraints)) {
082                    return Collections.singleton(superTypeOfAllLowerBounds);
083                }
084            }
085            return values;
086        }
087    
088        @Nullable
089        private static JetType commonSupertype(@NotNull Collection<JetType> lowerBounds) {
090            if (lowerBounds.isEmpty()) return null;
091            if (lowerBounds.size() == 1) {
092                JetType type = lowerBounds.iterator().next();
093                if (type.getConstructor() instanceof IntersectionTypeConstructor) {
094                    return commonSupertype(type.getConstructor().getSupertypes());
095                }
096            }
097            return CommonSupertypes.commonSupertype(lowerBounds);
098        }
099    
100        @Nullable
101        private static JetType commonSupertypeForNumberTypes(@NotNull Collection<JetType> numberLowerBounds) {
102            if (numberLowerBounds.isEmpty()) return null;
103            return TypeUtils.commonSupertypeForNumberTypes(numberLowerBounds);
104        }
105    
106        private static boolean trySuggestion(
107                @Nullable JetType suggestion,
108                @NotNull TypeConstraints typeConstraints
109        ) {
110            if (suggestion == null) return false;
111            if (!suggestion.getConstructor().isDenotable()) return false;
112            if (typeConstraints.getExactBounds().size() > 1) return false;
113    
114            for (JetType exactBound : typeConstraints.getExactBounds()) {
115                if (!JetTypeChecker.INSTANCE.equalTypes(exactBound, suggestion)) {
116                    return false;
117                }
118            }
119            for (JetType lowerBound : typeConstraints.getLowerBounds()) {
120                if (!JetTypeChecker.INSTANCE.isSubtypeOf(lowerBound, suggestion)) {
121                    return false;
122                }
123            }
124            for (JetType upperBound : typeConstraints.getUpperBounds()) {
125                if (!JetTypeChecker.INSTANCE.isSubtypeOf(suggestion, upperBound)) {
126                    return false;
127                }
128            }
129            return true;
130        }
131    
132        @NotNull
133        private static TypeConstraints filterNotContainingErrorType(
134                @NotNull TypeConstraints typeConstraints,
135                @NotNull Collection<JetType> values
136        ) {
137            TypeConstraintsImpl typeConstraintsWithoutErrorType = new TypeConstraintsImpl(typeConstraints.getVarianceOfPosition());
138            Collection<Pair<TypeConstraintsImpl.BoundKind, JetType>> allBounds = ((TypeConstraintsImpl) typeConstraints).getAllBounds();
139            for (Pair<TypeConstraintsImpl.BoundKind, JetType> pair : allBounds) {
140                TypeConstraintsImpl.BoundKind boundKind = pair.getFirst();
141                JetType type = pair.getSecond();
142                if (ErrorUtils.containsErrorType(type)) {
143                    values.add(type);
144                }
145                else if (type != null) {
146                    typeConstraintsWithoutErrorType.addBound(boundKind, type);
147                }
148            }
149            return typeConstraintsWithoutErrorType;
150        }
151    
152        @Nullable
153        public static JetType getValue(@Nullable TypeConstraints typeConstraints) {
154            //todo all checks
155            //todo variance dependance
156            if (typeConstraints == null) {
157                //todo assert typeConstraints != null;
158                return null;
159            }
160            Set<JetType> values = getValues(typeConstraints);
161            if (values.size() == 1) {
162                return values.iterator().next();
163            }
164            return null;
165        }
166    
167    
168    
169        @Nullable
170        public static TypeParameterDescriptor getFirstConflictingParameter(@NotNull ConstraintSystem constraintSystem) {
171            for (TypeParameterDescriptor typeParameter : constraintSystem.getTypeVariables()) {
172                TypeConstraints constraints = constraintSystem.getTypeConstraints(typeParameter);
173                if (getValues(constraints).size() > 1) {
174                    return typeParameter;
175                }
176            }
177            return null;
178        }
179    
180        @NotNull
181        public static Collection<TypeSubstitutor> getSubstitutorsForConflictingParameters(@NotNull ConstraintSystem constraintSystem) {
182            TypeParameterDescriptor firstConflictingParameter = getFirstConflictingParameter(constraintSystem);
183            if (firstConflictingParameter == null) return Collections.emptyList();
184    
185            Collection<JetType> conflictingTypes = getValues(constraintSystem.getTypeConstraints(firstConflictingParameter));
186    
187            ArrayList<Map<TypeConstructor, TypeProjection>> substitutionContexts = Lists.newArrayList();
188            for (JetType type : conflictingTypes) {
189                Map<TypeConstructor, TypeProjection> context = Maps.newLinkedHashMap();
190                context.put(firstConflictingParameter.getTypeConstructor(), new TypeProjection(type));
191                substitutionContexts.add(context);
192            }
193    
194            for (TypeParameterDescriptor typeParameter : constraintSystem.getTypeVariables()) {
195                if (typeParameter == firstConflictingParameter) continue;
196    
197                JetType safeType = getSafeValue(constraintSystem, typeParameter);
198                for (Map<TypeConstructor, TypeProjection> context : substitutionContexts) {
199                    TypeProjection typeProjection = new TypeProjection(safeType);
200                    context.put(typeParameter.getTypeConstructor(), typeProjection);
201                }
202            }
203            Collection<TypeSubstitutor> typeSubstitutors = Lists.newArrayList();
204            for (Map<TypeConstructor, TypeProjection> context : substitutionContexts) {
205                typeSubstitutors.add(TypeSubstitutor.create(context));
206            }
207            return typeSubstitutors;
208        }
209    
210        @NotNull
211        public static JetType getSafeValue(@NotNull ConstraintSystem constraintSystem, @NotNull TypeParameterDescriptor typeParameter) {
212            TypeConstraints constraints = constraintSystem.getTypeConstraints(typeParameter);
213            JetType type = getValue(constraints);
214            if (type != null) {
215                return type;
216            }
217            //todo may be error type
218            return typeParameter.getUpperBoundsAsType();
219        }
220    
221        public static boolean checkUpperBoundIsSatisfied(
222                @NotNull ConstraintSystem constraintSystem,
223                @NotNull TypeParameterDescriptor typeParameter,
224                boolean substituteOtherTypeParametersInBound
225        ) {
226            TypeConstraints typeConstraints = constraintSystem.getTypeConstraints(typeParameter);
227            assert typeConstraints != null;
228            JetType type = getValue(typeConstraints);
229            if (type == null) return true;
230            for (JetType upperBound : typeParameter.getUpperBounds()) {
231                if (!substituteOtherTypeParametersInBound && TypeUtils.dependsOnTypeParameters(upperBound, constraintSystem.getTypeVariables())) {
232                    continue;
233                }
234                JetType substitutedUpperBound = constraintSystem.getResultingSubstitutor().substitute(upperBound, Variance.INVARIANT);
235    
236                assert substitutedUpperBound != null : "We wanted to substitute projections as a result for " + typeParameter;
237                if (!JetTypeChecker.INSTANCE.isSubtypeOf(type, substitutedUpperBound)) {
238                    return false;
239                }
240            }
241            return true;
242        }
243    
244        public static boolean checkBoundsAreSatisfied(
245                @NotNull ConstraintSystem constraintSystem,
246                boolean substituteOtherTypeParametersInBounds
247        ) {
248            for (TypeParameterDescriptor typeVariable : constraintSystem.getTypeVariables()) {
249                if (!checkUpperBoundIsSatisfied(constraintSystem, typeVariable, substituteOtherTypeParametersInBounds)) {
250                    return false;
251                }
252            }
253            return true;
254        }
255    }