/*
* 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")
);
}
}