/* * Copyright 2010-2015 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.kotlin.types; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Predicate; public class TypeUnifier { public interface UnificationResult { boolean isSuccess(); @NotNull Map<TypeConstructor, TypeProjection> getSubstitution(); } /** * Finds a substitution S that turns {@code projectWithVariables} to {@code knownProjection}. * * Example: * known = List<String> * withVariables = List<X> * variables = {X} * * result = X -> String * * Only types accepted by {@code isVariable} are considered variables. */ @NotNull public static UnificationResult unify( @NotNull TypeProjection knownProjection, @NotNull TypeProjection projectWithVariables, @NotNull Predicate<TypeConstructor> isVariable ) { UnificationResultImpl result = new UnificationResultImpl(); doUnify(knownProjection, projectWithVariables, isVariable, result); return result; } private static void doUnify( TypeProjection knownProjection, TypeProjection projectWithVariables, Predicate<TypeConstructor> isVariable, UnificationResultImpl result ) { KotlinType known = knownProjection.getType(); KotlinType withVariables = projectWithVariables.getType(); // in Foo ~ in X => Foo ~ X Variance knownProjectionKind = knownProjection.getProjectionKind(); Variance withVariablesProjectionKind = projectWithVariables.getProjectionKind(); if (knownProjectionKind == withVariablesProjectionKind && knownProjectionKind != Variance.INVARIANT) { doUnify(new TypeProjectionImpl(known), new TypeProjectionImpl(withVariables), isVariable, result); return; } // Foo? ~ X? => Foo ~ X if (known.isMarkedNullable() && withVariables.isMarkedNullable()) { doUnify( new TypeProjectionImpl(knownProjectionKind, TypeUtils.makeNotNullable(known)), new TypeProjectionImpl(withVariablesProjectionKind, TypeUtils.makeNotNullable(withVariables)), isVariable, result ); return; } // in Foo ~ out X => fail // in Foo ~ X => may be OK if (knownProjectionKind != withVariablesProjectionKind && withVariablesProjectionKind != Variance.INVARIANT) { result.fail(); return; } // Foo ~ X? => fail if (!known.isMarkedNullable() && withVariables.isMarkedNullable()) { result.fail(); return; } // Foo ~ X => x |-> Foo TypeConstructor maybeVariable = withVariables.getConstructor(); if (isVariable.test(maybeVariable)) { result.put(maybeVariable, new TypeProjectionImpl(knownProjectionKind, known)); return; } // Foo? ~ Foo || in Foo ~ Foo || Foo ~ Bar boolean structuralMismatch = known.isMarkedNullable() != withVariables.isMarkedNullable() || knownProjectionKind != withVariablesProjectionKind || !known.getConstructor().equals(withVariables.getConstructor()); if (structuralMismatch) { result.fail(); return; } // Foo<A> ~ Foo<B, C> if (known.getArguments().size() != withVariables.getArguments().size()) { result.fail(); return; } // Foo ~ Foo if (known.getArguments().isEmpty()) { return; } // Foo<...> ~ Foo<...> List<TypeProjection> knownArguments = known.getArguments(); List<TypeProjection> withVariablesArguments = withVariables.getArguments(); for (int i = 0; i < knownArguments.size(); i++) { TypeProjection knownArg = knownArguments.get(i); TypeProjection withVariablesArg = withVariablesArguments.get(i); doUnify(knownArg, withVariablesArg, isVariable, result); } } private static class UnificationResultImpl implements UnificationResult { private boolean success = true; private final Map<TypeConstructor, TypeProjection> substitution = Maps.newHashMapWithExpectedSize(1); private final Set<TypeConstructor> failedVariables = Sets.newHashSetWithExpectedSize(0); @Override public boolean isSuccess() { return success; } public void fail() { success = false; } @Override @NotNull public Map<TypeConstructor, TypeProjection> getSubstitution() { return substitution; } public void put(TypeConstructor key, TypeProjection value) { if (failedVariables.contains(key)) return; TypeProjection oldValue = substitution.put(key, value); if (oldValue != null && !oldValue.equals(value)) { substitution.remove(key); failedVariables.add(key); fail(); } } } }