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.resolve.calls.inference;
018    
019    import com.google.common.collect.Lists;
020    import com.google.common.collect.Maps;
021    import kotlin.collections.CollectionsKt;
022    import kotlin.jvm.functions.Function1;
023    import org.jetbrains.annotations.NotNull;
024    import org.jetbrains.annotations.Nullable;
025    import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor;
026    import org.jetbrains.kotlin.psi.Call;
027    import org.jetbrains.kotlin.types.*;
028    import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
029    
030    import java.lang.reflect.InvocationTargetException;
031    import java.lang.reflect.Method;
032    import java.util.*;
033    
034    public class ConstraintsUtil {
035        @Nullable
036        public static TypeVariable getFirstConflictingVariable(@NotNull ConstraintSystem constraintSystem) {
037            for (TypeVariable typeVariable : constraintSystem.getTypeVariables()) {
038                TypeBounds constraints = constraintSystem.getTypeBounds(typeVariable);
039                if (constraints.getValues().size() > 1) {
040                    return typeVariable;
041                }
042            }
043            return null;
044        }
045    
046        @NotNull
047        public static Collection<TypeSubstitutor> getSubstitutorsForConflictingParameters(@NotNull ConstraintSystem constraintSystem) {
048            TypeVariable firstConflictingVariable = getFirstConflictingVariable(constraintSystem);
049            if (firstConflictingVariable == null) return Collections.emptyList();
050            TypeParameterDescriptor firstConflictingParameter = firstConflictingVariable.getOriginalTypeParameter();
051    
052            Collection<KotlinType> conflictingTypes = constraintSystem.getTypeBounds(firstConflictingVariable).getValues();
053    
054            List<Map<TypeConstructor, TypeProjection>> substitutionContexts = Lists.newArrayList();
055            for (KotlinType type : conflictingTypes) {
056                Map<TypeConstructor, TypeProjection> context = Maps.newLinkedHashMap();
057                context.put(firstConflictingParameter.getTypeConstructor(), new TypeProjectionImpl(type));
058                substitutionContexts.add(context);
059            }
060    
061            for (TypeVariable typeVariable : constraintSystem.getTypeVariables()) {
062                if (typeVariable == firstConflictingVariable) continue;
063    
064                KotlinType safeType = getSafeValue(constraintSystem, typeVariable);
065                for (Map<TypeConstructor, TypeProjection> context : substitutionContexts) {
066                    TypeProjection typeProjection = new TypeProjectionImpl(safeType);
067                    context.put(typeVariable.getOriginalTypeParameter().getTypeConstructor(), typeProjection);
068                }
069            }
070            Collection<TypeSubstitutor> typeSubstitutors = new ArrayList<TypeSubstitutor>(substitutionContexts.size());
071            for (Map<TypeConstructor, TypeProjection> context : substitutionContexts) {
072                typeSubstitutors.add(TypeSubstitutor.create(context));
073            }
074            return typeSubstitutors;
075        }
076    
077        @NotNull
078        private static KotlinType getSafeValue(@NotNull ConstraintSystem constraintSystem, @NotNull TypeVariable typeVariable) {
079            KotlinType type = constraintSystem.getTypeBounds(typeVariable).getValue();
080            if (type != null) {
081                return type;
082            }
083            //todo may be error type
084            return TypeIntersector.getUpperBoundsAsType(typeVariable.getOriginalTypeParameter());
085        }
086    
087        public static boolean checkUpperBoundIsSatisfied(
088                @NotNull ConstraintSystem constraintSystem,
089                @NotNull TypeParameterDescriptor typeParameter,
090                @NotNull Call call,
091                boolean substituteOtherTypeParametersInBound
092        ) {
093            TypeVariable typeVariable = ConstraintSystemUtilsKt.descriptorToVariable(
094                    constraintSystem, TypeVariableKt.toHandle(call), typeParameter
095            );
096            KotlinType type = constraintSystem.getTypeBounds(typeVariable).getValue();
097            if (type == null) return true;
098    
099            List<TypeParameterDescriptor> typeParametersUsedInSystem = CollectionsKt.map(
100                    constraintSystem.getTypeVariables(),
101                    new Function1<TypeVariable, TypeParameterDescriptor>() {
102                        @Override
103                        public TypeParameterDescriptor invoke(TypeVariable variable) {
104                            return variable.getOriginalTypeParameter();
105                        }
106                    }
107            );
108    
109            for (KotlinType upperBound : typeParameter.getUpperBounds()) {
110                if (!substituteOtherTypeParametersInBound &&
111                    TypeUtils.dependsOnTypeParameters(upperBound, typeParametersUsedInSystem)) {
112                    continue;
113                }
114                KotlinType substitutedUpperBound = constraintSystem.getResultingSubstitutor().substitute(upperBound, Variance.INVARIANT);
115    
116                assert substitutedUpperBound != null : "We wanted to substitute projections as a result for " + typeParameter;
117                if (!KotlinTypeChecker.DEFAULT.isSubtypeOf(type, substitutedUpperBound)) {
118                    return false;
119                }
120            }
121            return true;
122        }
123    
124        public static String getDebugMessageForStatus(@NotNull ConstraintSystemStatus status) {
125            StringBuilder sb = new StringBuilder();
126            List<Method> interestingMethods = Lists.newArrayList();
127            for (Method method : status.getClass().getMethods()) {
128                String name = method.getName();
129                boolean isInteresting = name.startsWith("is") || name.startsWith("has") && !name.equals("hashCode");
130                if (method.getParameterTypes().length == 0 && isInteresting) {
131                    interestingMethods.add(method);
132                }
133            }
134            Collections.sort(interestingMethods, new Comparator<Method>() {
135                @Override
136                public int compare(@NotNull Method method1, @NotNull Method method2) {
137                    return method1.getName().compareTo(method2.getName());
138                }
139            });
140            for (Iterator<Method> iterator = interestingMethods.iterator(); iterator.hasNext(); ) {
141                Method method = iterator.next();
142                try {
143                    sb.append("-").append(method.getName()).append(": ").append(method.invoke(status));
144                    if (iterator.hasNext()) {
145                        sb.append("\n");
146                    }
147                }
148                catch (IllegalAccessException e) {
149                    sb.append(e.getMessage());
150                }
151                catch (InvocationTargetException e) {
152                    sb.append(e.getMessage());
153                }
154            }
155            return sb.toString();
156        }
157    }