/* * Copyright 2010-2016 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.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import kotlin.Unit; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.analyzer.AnalysisResult; import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; import org.jetbrains.kotlin.descriptors.ClassDescriptor; import org.jetbrains.kotlin.descriptors.ClassifierDescriptor; import org.jetbrains.kotlin.descriptors.ModuleDescriptor; import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor; import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages; import org.jetbrains.kotlin.incremental.components.NoLookupLocation; import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.psi.KtPsiFactoryKt; import org.jetbrains.kotlin.psi.KtTypeReference; import org.jetbrains.kotlin.renderer.DescriptorRenderer; import org.jetbrains.kotlin.resolve.*; import org.jetbrains.kotlin.resolve.calls.results.TypeSpecificityComparator; import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil; import org.jetbrains.kotlin.resolve.scopes.*; import org.jetbrains.kotlin.resolve.scopes.utils.ScopeUtilsKt; import org.jetbrains.kotlin.test.ConfigurationKind; import org.jetbrains.kotlin.test.KotlinTestUtils; import org.jetbrains.kotlin.test.KotlinTestWithEnvironment; import org.jetbrains.kotlin.tests.di.ContainerForTests; import org.jetbrains.kotlin.tests.di.InjectionKt; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Map; @SuppressWarnings("unchecked") public class TypeSubstitutorTest extends KotlinTestWithEnvironment { private LexicalScope scope; private ContainerForTests container; @Override protected KotlinCoreEnvironment createEnvironment() { return createEnvironmentWithMockJdk(ConfigurationKind.JDK_ONLY); } @Override protected void setUp() throws Exception { super.setUp(); container = InjectionKt.createContainerForTests(getProject(), KotlinTestUtils.createEmptyModule()); scope = getContextScope(); } @Override protected void tearDown() throws Exception { container = null; scope = null; super.tearDown(); } private LexicalScope getContextScope() throws IOException { // todo comments String text = FileUtil.loadFile(new File("compiler/testData/type-substitutor.kt"), true); KtFile ktFile = KtPsiFactoryKt.KtPsiFactory(getProject()).createFile(text); AnalysisResult analysisResult = JvmResolveUtil.analyze(ktFile, getEnvironment()); ModuleDescriptor module = analysisResult.getModuleDescriptor(); LexicalScope topLevelScope = analysisResult.getBindingContext().get(BindingContext.LEXICAL_SCOPE, ktFile); ClassifierDescriptor contextClass = ScopeUtilsKt.findClassifier(topLevelScope, Name.identifier("___Context"), NoLookupLocation.FROM_TEST); assert contextClass instanceof ClassDescriptor; LocalRedeclarationChecker redeclarationChecker = new ThrowingLocalRedeclarationChecker(new OverloadChecker(TypeSpecificityComparator.NONE.INSTANCE)); LexicalScope typeParameters = new LexicalScopeImpl( topLevelScope, module, false, null, LexicalScopeKind.SYNTHETIC, redeclarationChecker, handler -> { for (TypeParameterDescriptor parameterDescriptor : contextClass.getTypeConstructor().getParameters()) { handler.addClassifierDescriptor(parameterDescriptor); } return Unit.INSTANCE; } ); return new LexicalChainedScope( typeParameters, module, false, null, LexicalScopeKind.SYNTHETIC, Arrays.asList( contextClass.getDefaultType().getMemberScope(), module.getBuiltIns().getBuiltInsPackageScope() ) ); } private void doTest(@Nullable String expectedTypeStr, String initialTypeStr, Pair<String, String>... substitutionStrs) { KotlinType initialType = resolveType(initialTypeStr); Map<TypeConstructor, TypeProjection> map = stringsToSubstitutionMap(substitutionStrs); TypeSubstitutor substitutor = TypeSubstitutor.create(map); KotlinType result = substitutor.substitute(initialType, Variance.INVARIANT); if (expectedTypeStr == null) { assertNull(result); } else { assertNotNull(result); assertEquals(expectedTypeStr, DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(result)); } } private Map<TypeConstructor, TypeProjection> stringsToSubstitutionMap(Pair<String, String>[] substitutionStrs) { Map<TypeConstructor, TypeProjection> map = Maps.newHashMap(); for (Pair<String, String> pair : substitutionStrs) { String typeParameterName = pair.first; String replacementProjectionString = pair.second; ClassifierDescriptor classifier = ScopeUtilsKt.findClassifier(scope, Name.identifier(typeParameterName), NoLookupLocation.FROM_TEST); assertNotNull("No type parameter named " + typeParameterName, classifier); assertTrue(typeParameterName + " is not a type parameter: " + classifier, classifier instanceof TypeParameterDescriptor); String typeStr = "C<" + replacementProjectionString + ">"; KotlinType typeWithArgument = resolveType(typeStr); assert !typeWithArgument.getArguments().isEmpty() : "No arguments: " + typeWithArgument + " from " + typeStr; map.put(classifier.getTypeConstructor(), typeWithArgument.getArguments().get(0)); } return map; } private KotlinType resolveType(String typeStr) { KtTypeReference jetTypeReference = KtPsiFactoryKt.KtPsiFactory(getProject()).createType(typeStr); AnalyzingUtils.checkForSyntacticErrors(jetTypeReference); BindingTrace trace = new BindingTraceContext(); KotlinType type = container.getTypeResolver().resolveType(scope, jetTypeReference, trace, true); if (!trace.getBindingContext().getDiagnostics().isEmpty()) { fail("Errors:\n" + StringUtil.join(trace.getBindingContext().getDiagnostics(), DefaultErrorMessages::render, "\n")); } return type; } @NotNull private static Pair<String, String> map(String typeParameterName, String replacementProjectionString) { return Pair.create(typeParameterName, replacementProjectionString); } public void testNoOccurrence() throws Exception { doTest( "C<Int>", "C<Int>", map("T", "String") ); } public void testSimpleOccurrence() throws Exception { doTest( "C<String>", "C<T>", map("T", "String") ); } public void testSimpleOutProjectionInReplacement() throws Exception { doTest( "C<out String>", "C<T>", map("T", "out String") ); } public void testSimpleInProjectionInReplacement() throws Exception { doTest( "C<in String>", "C<T>", map("T", "in String") ); } public void testSimpleOutProjectionInSubject() throws Exception { doTest( "C<out String>", "C<out T>", map("T", "String") ); } public void testSimpleInProjectionInSubject() throws Exception { doTest( "C<in String>", "C<in T>", map("T", "String") ); } public void testOutOutProjection() throws Exception { doTest( "C<out String>", "C<out T>", map("T", "out String") ); } public void testInInProjection() throws Exception { doTest( "C<in String>", "C<in T>", map("T", "in String") ); } public void testInOutProjection() throws Exception { doTest( null, "C<in T>", map("T", "out String") ); } public void testOutInProjection() throws Exception { doTest( "C<out Any?>", "C<out T>", map("T", "in String") ); } public void testOutOutProjectionDeclarationSite() throws Exception { doTest( "Out<String>", "Out<T>", map("T", "out String") ); } public void testInInProjectionDeclarationSite() throws Exception { doTest( "In<String>", "In<T>", map("T", "in String") ); } public void testInOutProjectionDeclarationSite() throws Exception { doTest( "In<*>", "In<T>", map("T", "out String") ); } public void testOutInProjectionDeclarationSite() throws Exception { doTest( "Out<*>", "Out<T>", map("T", "in String") ); } public void testTwoParameters() throws Exception { doTest( "P<Int, String>", "P<T, R>", map("T", "Int"), map("R", "String") ); } public void testDeepType() throws Exception { doTest( "C<P<Int, P<Int, String>>>", "C<P<T, P<T, R>>>", map("T", "Int"), map("R", "String") ); } public void testShallowType() throws Exception { doTest( "String", "T", map("T", "String") ); } public void testShallowTypeNullable() throws Exception { doTest( "String?", "T?", map("T", "String") ); } public void testShallowTypeNullableReplacement() throws Exception { doTest( "String?", "T", map("T", "String?") ); } public void testShallowTypeNullableOnBothEnds() throws Exception { doTest( "String?", "T?", map("T", "String?") ); } public void testNothingType() throws Exception { doTest( "Nothing", "Nothing", map("T", "String") ); } public void testCallSiteNullable() throws Exception { doTest( "C<String?>", "C<T?>", map("T", "String") ); } public void testReplacementNullable() throws Exception { doTest( "C<String?>", "C<T>", map("T", "String?") ); } public void testCallSiteNullableWithProjection() throws Exception { doTest( "C<out String?>", "C<T?>", map("T", "out String") ); } public void testReplacementNullableWithProjection() throws Exception { doTest( "C<out String?>", "C<out T>", map("T", "String?") ); } public void testCallSiteNullableWithConsumedProjection() throws Exception { doTest( "Out<String?>", "Out<T?>", map("T", "out String") ); } public void testReplacementNullableConsumedProjection() throws Exception { doTest( "In<String?>", "In<T>", map("T", "in String?") ); } //public void testTwoParametersInChain() throws Exception { // doTest( // "P<Int, Int>", // "P<T, R>", // map("T", "Int"), // map("R", "T") // ); //} public void testStarProjection() throws Exception { doTest( "Rec<*>", "Rec<*>", map("T", "String") ); } public void testStarProjectionOut() throws Exception { doTest( "Out<*>", "Out<*>", map("T", "String") ); } }