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    }